From c570e04d1fab5b6088f66baa177dcc1f9f0deefa Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 15:37:01 +0200 Subject: [PATCH 01/16] refactor: change StmtHandler signature to use *ExecContext Replace *Executor parameter with *ExecContext in StmtHandler type, Registry.Dispatch, and all 115 handler wrappers. Handlers access the Executor via ctx.executor during the migration period. Add newExecContext bridge that builds ExecContext from Executor state with the timeout context from Execute(). --- mdl/executor/exec_context.go | 6 + mdl/executor/executor.go | 2 +- mdl/executor/executor_dispatch.go | 28 +- mdl/executor/register_stubs.go | 460 +++++++++++++++--------------- mdl/executor/registry.go | 6 +- mdl/executor/registry_test.go | 12 +- 6 files changed, 271 insertions(+), 243 deletions(-) diff --git a/mdl/executor/exec_context.go b/mdl/executor/exec_context.go index 4485d332..24881476 100644 --- a/mdl/executor/exec_context.go +++ b/mdl/executor/exec_context.go @@ -47,4 +47,10 @@ type ExecContext struct { // Cache holds per-session cached data for performance. Cache *executorCache + + // executor is a temporary back-reference used during incremental migration. + // Handlers that have not yet been migrated to use Backend can access the + // original Executor through this field. It will be removed once all handlers + // are migrated. + executor *Executor } diff --git a/mdl/executor/executor.go b/mdl/executor/executor.go index c49f244d..2f918b01 100644 --- a/mdl/executor/executor.go +++ b/mdl/executor/executor.go @@ -199,7 +199,7 @@ func (e *Executor) Execute(stmt ast.Statement) error { type result struct{ err error } ch := make(chan result, 1) go func() { - ch <- result{e.executeInner(stmt)} + ch <- result{e.executeInner(ctx, stmt)} }() var err error diff --git a/mdl/executor/executor_dispatch.go b/mdl/executor/executor_dispatch.go index a260b75e..b22c05bb 100644 --- a/mdl/executor/executor_dispatch.go +++ b/mdl/executor/executor_dispatch.go @@ -2,9 +2,31 @@ package executor -import "github.com/mendixlabs/mxcli/mdl/ast" +import ( + "context" + + "github.com/mendixlabs/mxcli/mdl/ast" +) // executeInner dispatches a statement to its registered handler. -func (e *Executor) executeInner(stmt ast.Statement) error { - return e.registry.Dispatch(e, stmt) +func (e *Executor) executeInner(ctx context.Context, stmt ast.Statement) error { + ectx := e.newExecContext(ctx) + return e.registry.Dispatch(ectx, stmt) +} + +// newExecContext builds an ExecContext from the current Executor state. +// During the migration period, Backend may be nil; handlers access the +// Executor via ctx.executor for operations not yet routed through Backend. +func (e *Executor) newExecContext(ctx context.Context) *ExecContext { + return &ExecContext{ + Context: ctx, + Output: e.output, + Format: e.format, + Quiet: e.quiet, + Logger: e.logger, + Fragments: e.fragments, + Catalog: e.catalog, + Cache: e.cache, + executor: e, + } } diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 44cb28e2..0a2cf0e9 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -10,435 +10,435 @@ import "github.com/mendixlabs/mxcli/mdl/ast" // by direct function references. func registerConnectionHandlers(r *Registry) { - r.Register(&ast.ConnectStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execConnect(stmt.(*ast.ConnectStmt)) + r.Register(&ast.ConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execConnect(stmt.(*ast.ConnectStmt)) }) - r.Register(&ast.DisconnectStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDisconnect() + r.Register(&ast.DisconnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDisconnect() }) - r.Register(&ast.StatusStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execStatus() + r.Register(&ast.StatusStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execStatus() }) } func registerModuleHandlers(r *Registry) { - r.Register(&ast.CreateModuleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateModule(stmt.(*ast.CreateModuleStmt)) + r.Register(&ast.CreateModuleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateModule(stmt.(*ast.CreateModuleStmt)) }) - r.Register(&ast.DropModuleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropModule(stmt.(*ast.DropModuleStmt)) + r.Register(&ast.DropModuleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropModule(stmt.(*ast.DropModuleStmt)) }) } func registerEnumerationHandlers(r *Registry) { - r.Register(&ast.CreateEnumerationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateEnumeration(stmt.(*ast.CreateEnumerationStmt)) + r.Register(&ast.CreateEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateEnumeration(stmt.(*ast.CreateEnumerationStmt)) }) - r.Register(&ast.AlterEnumerationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterEnumeration(stmt.(*ast.AlterEnumerationStmt)) + r.Register(&ast.AlterEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterEnumeration(stmt.(*ast.AlterEnumerationStmt)) }) - r.Register(&ast.DropEnumerationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropEnumeration(stmt.(*ast.DropEnumerationStmt)) + r.Register(&ast.DropEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropEnumeration(stmt.(*ast.DropEnumerationStmt)) }) } func registerConstantHandlers(r *Registry) { - r.Register(&ast.CreateConstantStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createConstant(stmt.(*ast.CreateConstantStmt)) + r.Register(&ast.CreateConstantStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createConstant(stmt.(*ast.CreateConstantStmt)) }) - r.Register(&ast.DropConstantStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropConstant(stmt.(*ast.DropConstantStmt)) + r.Register(&ast.DropConstantStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropConstant(stmt.(*ast.DropConstantStmt)) }) } func registerDatabaseConnectionHandlers(r *Registry) { - r.Register(&ast.CreateDatabaseConnectionStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createDatabaseConnection(stmt.(*ast.CreateDatabaseConnectionStmt)) + r.Register(&ast.CreateDatabaseConnectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createDatabaseConnection(stmt.(*ast.CreateDatabaseConnectionStmt)) }) } func registerEntityHandlers(r *Registry) { - r.Register(&ast.CreateEntityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateEntity(stmt.(*ast.CreateEntityStmt)) + r.Register(&ast.CreateEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateEntity(stmt.(*ast.CreateEntityStmt)) }) - r.Register(&ast.CreateViewEntityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateViewEntity(stmt.(*ast.CreateViewEntityStmt)) + r.Register(&ast.CreateViewEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateViewEntity(stmt.(*ast.CreateViewEntityStmt)) }) - r.Register(&ast.AlterEntityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterEntity(stmt.(*ast.AlterEntityStmt)) + r.Register(&ast.AlterEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterEntity(stmt.(*ast.AlterEntityStmt)) }) - r.Register(&ast.DropEntityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropEntity(stmt.(*ast.DropEntityStmt)) + r.Register(&ast.DropEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropEntity(stmt.(*ast.DropEntityStmt)) }) } func registerAssociationHandlers(r *Registry) { - r.Register(&ast.CreateAssociationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateAssociation(stmt.(*ast.CreateAssociationStmt)) + r.Register(&ast.CreateAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateAssociation(stmt.(*ast.CreateAssociationStmt)) }) - r.Register(&ast.AlterAssociationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterAssociation(stmt.(*ast.AlterAssociationStmt)) + r.Register(&ast.AlterAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterAssociation(stmt.(*ast.AlterAssociationStmt)) }) - r.Register(&ast.DropAssociationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropAssociation(stmt.(*ast.DropAssociationStmt)) + r.Register(&ast.DropAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropAssociation(stmt.(*ast.DropAssociationStmt)) }) } func registerMicroflowHandlers(r *Registry) { - r.Register(&ast.CreateMicroflowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateMicroflow(stmt.(*ast.CreateMicroflowStmt)) + r.Register(&ast.CreateMicroflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateMicroflow(stmt.(*ast.CreateMicroflowStmt)) }) - r.Register(&ast.DropMicroflowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropMicroflow(stmt.(*ast.DropMicroflowStmt)) + r.Register(&ast.DropMicroflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropMicroflow(stmt.(*ast.DropMicroflowStmt)) }) } func registerPageHandlers(r *Registry) { - r.Register(&ast.CreatePageStmtV3{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreatePageV3(stmt.(*ast.CreatePageStmtV3)) + r.Register(&ast.CreatePageStmtV3{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreatePageV3(stmt.(*ast.CreatePageStmtV3)) }) - r.Register(&ast.DropPageStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropPage(stmt.(*ast.DropPageStmt)) + r.Register(&ast.DropPageStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropPage(stmt.(*ast.DropPageStmt)) }) - r.Register(&ast.CreateSnippetStmtV3{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateSnippetV3(stmt.(*ast.CreateSnippetStmtV3)) + r.Register(&ast.CreateSnippetStmtV3{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateSnippetV3(stmt.(*ast.CreateSnippetStmtV3)) }) - r.Register(&ast.DropSnippetStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropSnippet(stmt.(*ast.DropSnippetStmt)) + r.Register(&ast.DropSnippetStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropSnippet(stmt.(*ast.DropSnippetStmt)) }) - r.Register(&ast.DropJavaActionStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropJavaAction(stmt.(*ast.DropJavaActionStmt)) + r.Register(&ast.DropJavaActionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropJavaAction(stmt.(*ast.DropJavaActionStmt)) }) - r.Register(&ast.CreateJavaActionStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateJavaAction(stmt.(*ast.CreateJavaActionStmt)) + r.Register(&ast.CreateJavaActionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateJavaAction(stmt.(*ast.CreateJavaActionStmt)) }) - r.Register(&ast.DropFolderStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropFolder(stmt.(*ast.DropFolderStmt)) + r.Register(&ast.DropFolderStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropFolder(stmt.(*ast.DropFolderStmt)) }) - r.Register(&ast.MoveFolderStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execMoveFolder(stmt.(*ast.MoveFolderStmt)) + r.Register(&ast.MoveFolderStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execMoveFolder(stmt.(*ast.MoveFolderStmt)) }) - r.Register(&ast.MoveStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execMove(stmt.(*ast.MoveStmt)) + r.Register(&ast.MoveStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execMove(stmt.(*ast.MoveStmt)) }) - r.Register(&ast.RenameStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRename(stmt.(*ast.RenameStmt)) + r.Register(&ast.RenameStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRename(stmt.(*ast.RenameStmt)) }) } func registerSecurityHandlers(r *Registry) { - r.Register(&ast.CreateModuleRoleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateModuleRole(stmt.(*ast.CreateModuleRoleStmt)) + r.Register(&ast.CreateModuleRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateModuleRole(stmt.(*ast.CreateModuleRoleStmt)) }) - r.Register(&ast.DropModuleRoleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropModuleRole(stmt.(*ast.DropModuleRoleStmt)) + r.Register(&ast.DropModuleRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropModuleRole(stmt.(*ast.DropModuleRoleStmt)) }) - r.Register(&ast.CreateUserRoleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateUserRole(stmt.(*ast.CreateUserRoleStmt)) + r.Register(&ast.CreateUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateUserRole(stmt.(*ast.CreateUserRoleStmt)) }) - r.Register(&ast.AlterUserRoleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterUserRole(stmt.(*ast.AlterUserRoleStmt)) + r.Register(&ast.AlterUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterUserRole(stmt.(*ast.AlterUserRoleStmt)) }) - r.Register(&ast.DropUserRoleStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropUserRole(stmt.(*ast.DropUserRoleStmt)) + r.Register(&ast.DropUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropUserRole(stmt.(*ast.DropUserRoleStmt)) }) - r.Register(&ast.GrantEntityAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantEntityAccess(stmt.(*ast.GrantEntityAccessStmt)) + r.Register(&ast.GrantEntityAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantEntityAccess(stmt.(*ast.GrantEntityAccessStmt)) }) - r.Register(&ast.RevokeEntityAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokeEntityAccess(stmt.(*ast.RevokeEntityAccessStmt)) + r.Register(&ast.RevokeEntityAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokeEntityAccess(stmt.(*ast.RevokeEntityAccessStmt)) }) - r.Register(&ast.GrantMicroflowAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantMicroflowAccess(stmt.(*ast.GrantMicroflowAccessStmt)) + r.Register(&ast.GrantMicroflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantMicroflowAccess(stmt.(*ast.GrantMicroflowAccessStmt)) }) - r.Register(&ast.RevokeMicroflowAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokeMicroflowAccess(stmt.(*ast.RevokeMicroflowAccessStmt)) + r.Register(&ast.RevokeMicroflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokeMicroflowAccess(stmt.(*ast.RevokeMicroflowAccessStmt)) }) - r.Register(&ast.GrantPageAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantPageAccess(stmt.(*ast.GrantPageAccessStmt)) + r.Register(&ast.GrantPageAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantPageAccess(stmt.(*ast.GrantPageAccessStmt)) }) - r.Register(&ast.RevokePageAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokePageAccess(stmt.(*ast.RevokePageAccessStmt)) + r.Register(&ast.RevokePageAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokePageAccess(stmt.(*ast.RevokePageAccessStmt)) }) - r.Register(&ast.GrantWorkflowAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantWorkflowAccess(stmt.(*ast.GrantWorkflowAccessStmt)) + r.Register(&ast.GrantWorkflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantWorkflowAccess(stmt.(*ast.GrantWorkflowAccessStmt)) }) - r.Register(&ast.RevokeWorkflowAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokeWorkflowAccess(stmt.(*ast.RevokeWorkflowAccessStmt)) + r.Register(&ast.RevokeWorkflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokeWorkflowAccess(stmt.(*ast.RevokeWorkflowAccessStmt)) }) - r.Register(&ast.AlterProjectSecurityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterProjectSecurity(stmt.(*ast.AlterProjectSecurityStmt)) + r.Register(&ast.AlterProjectSecurityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterProjectSecurity(stmt.(*ast.AlterProjectSecurityStmt)) }) - r.Register(&ast.CreateDemoUserStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateDemoUser(stmt.(*ast.CreateDemoUserStmt)) + r.Register(&ast.CreateDemoUserStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateDemoUser(stmt.(*ast.CreateDemoUserStmt)) }) - r.Register(&ast.DropDemoUserStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropDemoUser(stmt.(*ast.DropDemoUserStmt)) + r.Register(&ast.DropDemoUserStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropDemoUser(stmt.(*ast.DropDemoUserStmt)) }) - r.Register(&ast.UpdateSecurityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execUpdateSecurity(stmt.(*ast.UpdateSecurityStmt)) + r.Register(&ast.UpdateSecurityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execUpdateSecurity(stmt.(*ast.UpdateSecurityStmt)) }) } func registerNavigationHandlers(r *Registry) { - r.Register(&ast.AlterNavigationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterNavigation(stmt.(*ast.AlterNavigationStmt)) + r.Register(&ast.AlterNavigationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterNavigation(stmt.(*ast.AlterNavigationStmt)) }) } func registerImageHandlers(r *Registry) { - r.Register(&ast.CreateImageCollectionStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateImageCollection(stmt.(*ast.CreateImageCollectionStmt)) + r.Register(&ast.CreateImageCollectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateImageCollection(stmt.(*ast.CreateImageCollectionStmt)) }) - r.Register(&ast.DropImageCollectionStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropImageCollection(stmt.(*ast.DropImageCollectionStmt)) + r.Register(&ast.DropImageCollectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropImageCollection(stmt.(*ast.DropImageCollectionStmt)) }) } func registerWorkflowHandlers(r *Registry) { - r.Register(&ast.CreateWorkflowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateWorkflow(stmt.(*ast.CreateWorkflowStmt)) + r.Register(&ast.CreateWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateWorkflow(stmt.(*ast.CreateWorkflowStmt)) }) - r.Register(&ast.DropWorkflowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropWorkflow(stmt.(*ast.DropWorkflowStmt)) + r.Register(&ast.DropWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropWorkflow(stmt.(*ast.DropWorkflowStmt)) }) - r.Register(&ast.AlterWorkflowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterWorkflow(stmt.(*ast.AlterWorkflowStmt)) + r.Register(&ast.AlterWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterWorkflow(stmt.(*ast.AlterWorkflowStmt)) }) } func registerBusinessEventHandlers(r *Registry) { - r.Register(&ast.CreateBusinessEventServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createBusinessEventService(stmt.(*ast.CreateBusinessEventServiceStmt)) + r.Register(&ast.CreateBusinessEventServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createBusinessEventService(stmt.(*ast.CreateBusinessEventServiceStmt)) }) - r.Register(&ast.DropBusinessEventServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropBusinessEventService(stmt.(*ast.DropBusinessEventServiceStmt)) + r.Register(&ast.DropBusinessEventServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropBusinessEventService(stmt.(*ast.DropBusinessEventServiceStmt)) }) } func registerSettingsHandlers(r *Registry) { - r.Register(&ast.AlterSettingsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.alterSettings(stmt.(*ast.AlterSettingsStmt)) + r.Register(&ast.AlterSettingsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.alterSettings(stmt.(*ast.AlterSettingsStmt)) }) - r.Register(&ast.CreateConfigurationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createConfiguration(stmt.(*ast.CreateConfigurationStmt)) + r.Register(&ast.CreateConfigurationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createConfiguration(stmt.(*ast.CreateConfigurationStmt)) }) - r.Register(&ast.DropConfigurationStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropConfiguration(stmt.(*ast.DropConfigurationStmt)) + r.Register(&ast.DropConfigurationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropConfiguration(stmt.(*ast.DropConfigurationStmt)) }) } func registerODataHandlers(r *Registry) { - r.Register(&ast.CreateODataClientStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createODataClient(stmt.(*ast.CreateODataClientStmt)) + r.Register(&ast.CreateODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createODataClient(stmt.(*ast.CreateODataClientStmt)) }) - r.Register(&ast.AlterODataClientStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.alterODataClient(stmt.(*ast.AlterODataClientStmt)) + r.Register(&ast.AlterODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.alterODataClient(stmt.(*ast.AlterODataClientStmt)) }) - r.Register(&ast.DropODataClientStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropODataClient(stmt.(*ast.DropODataClientStmt)) + r.Register(&ast.DropODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropODataClient(stmt.(*ast.DropODataClientStmt)) }) - r.Register(&ast.CreateODataServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createODataService(stmt.(*ast.CreateODataServiceStmt)) + r.Register(&ast.CreateODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createODataService(stmt.(*ast.CreateODataServiceStmt)) }) - r.Register(&ast.AlterODataServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.alterODataService(stmt.(*ast.AlterODataServiceStmt)) + r.Register(&ast.AlterODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.alterODataService(stmt.(*ast.AlterODataServiceStmt)) }) - r.Register(&ast.DropODataServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropODataService(stmt.(*ast.DropODataServiceStmt)) + r.Register(&ast.DropODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropODataService(stmt.(*ast.DropODataServiceStmt)) }) } func registerJSONStructureHandlers(r *Registry) { - r.Register(&ast.CreateJsonStructureStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateJsonStructure(stmt.(*ast.CreateJsonStructureStmt)) + r.Register(&ast.CreateJsonStructureStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateJsonStructure(stmt.(*ast.CreateJsonStructureStmt)) }) - r.Register(&ast.DropJsonStructureStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropJsonStructure(stmt.(*ast.DropJsonStructureStmt)) + r.Register(&ast.DropJsonStructureStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropJsonStructure(stmt.(*ast.DropJsonStructureStmt)) }) } func registerMappingHandlers(r *Registry) { - r.Register(&ast.CreateImportMappingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateImportMapping(stmt.(*ast.CreateImportMappingStmt)) + r.Register(&ast.CreateImportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateImportMapping(stmt.(*ast.CreateImportMappingStmt)) }) - r.Register(&ast.DropImportMappingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropImportMapping(stmt.(*ast.DropImportMappingStmt)) + r.Register(&ast.DropImportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropImportMapping(stmt.(*ast.DropImportMappingStmt)) }) - r.Register(&ast.CreateExportMappingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateExportMapping(stmt.(*ast.CreateExportMappingStmt)) + r.Register(&ast.CreateExportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateExportMapping(stmt.(*ast.CreateExportMappingStmt)) }) - r.Register(&ast.DropExportMappingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropExportMapping(stmt.(*ast.DropExportMappingStmt)) + r.Register(&ast.DropExportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropExportMapping(stmt.(*ast.DropExportMappingStmt)) }) } func registerRESTHandlers(r *Registry) { - r.Register(&ast.CreateRestClientStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createRestClient(stmt.(*ast.CreateRestClientStmt)) + r.Register(&ast.CreateRestClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createRestClient(stmt.(*ast.CreateRestClientStmt)) }) - r.Register(&ast.DropRestClientStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.dropRestClient(stmt.(*ast.DropRestClientStmt)) + r.Register(&ast.DropRestClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.dropRestClient(stmt.(*ast.DropRestClientStmt)) }) - r.Register(&ast.CreatePublishedRestServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreatePublishedRestService(stmt.(*ast.CreatePublishedRestServiceStmt)) + r.Register(&ast.CreatePublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreatePublishedRestService(stmt.(*ast.CreatePublishedRestServiceStmt)) }) - r.Register(&ast.DropPublishedRestServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropPublishedRestService(stmt.(*ast.DropPublishedRestServiceStmt)) + r.Register(&ast.DropPublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropPublishedRestService(stmt.(*ast.DropPublishedRestServiceStmt)) }) - r.Register(&ast.AlterPublishedRestServiceStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterPublishedRestService(stmt.(*ast.AlterPublishedRestServiceStmt)) + r.Register(&ast.AlterPublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterPublishedRestService(stmt.(*ast.AlterPublishedRestServiceStmt)) }) - r.Register(&ast.CreateExternalEntityStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateExternalEntity(stmt.(*ast.CreateExternalEntityStmt)) + r.Register(&ast.CreateExternalEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateExternalEntity(stmt.(*ast.CreateExternalEntityStmt)) }) - r.Register(&ast.CreateExternalEntitiesStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.createExternalEntities(stmt.(*ast.CreateExternalEntitiesStmt)) + r.Register(&ast.CreateExternalEntitiesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.createExternalEntities(stmt.(*ast.CreateExternalEntitiesStmt)) }) - r.Register(&ast.GrantODataServiceAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantODataServiceAccess(stmt.(*ast.GrantODataServiceAccessStmt)) + r.Register(&ast.GrantODataServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantODataServiceAccess(stmt.(*ast.GrantODataServiceAccessStmt)) }) - r.Register(&ast.RevokeODataServiceAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokeODataServiceAccess(stmt.(*ast.RevokeODataServiceAccessStmt)) + r.Register(&ast.RevokeODataServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokeODataServiceAccess(stmt.(*ast.RevokeODataServiceAccessStmt)) }) - r.Register(&ast.GrantPublishedRestServiceAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execGrantPublishedRestServiceAccess(stmt.(*ast.GrantPublishedRestServiceAccessStmt)) + r.Register(&ast.GrantPublishedRestServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execGrantPublishedRestServiceAccess(stmt.(*ast.GrantPublishedRestServiceAccessStmt)) }) - r.Register(&ast.RevokePublishedRestServiceAccessStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRevokePublishedRestServiceAccess(stmt.(*ast.RevokePublishedRestServiceAccessStmt)) + r.Register(&ast.RevokePublishedRestServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRevokePublishedRestServiceAccess(stmt.(*ast.RevokePublishedRestServiceAccessStmt)) }) } func registerDataTransformerHandlers(r *Registry) { - r.Register(&ast.CreateDataTransformerStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCreateDataTransformer(stmt.(*ast.CreateDataTransformerStmt)) + r.Register(&ast.CreateDataTransformerStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCreateDataTransformer(stmt.(*ast.CreateDataTransformerStmt)) }) - r.Register(&ast.DropDataTransformerStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDropDataTransformer(stmt.(*ast.DropDataTransformerStmt)) + r.Register(&ast.DropDataTransformerStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDropDataTransformer(stmt.(*ast.DropDataTransformerStmt)) }) } func registerQueryHandlers(r *Registry) { - r.Register(&ast.ShowStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execShow(stmt.(*ast.ShowStmt)) + r.Register(&ast.ShowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execShow(stmt.(*ast.ShowStmt)) }) - r.Register(&ast.ShowWidgetsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execShowWidgets(stmt.(*ast.ShowWidgetsStmt)) + r.Register(&ast.ShowWidgetsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execShowWidgets(stmt.(*ast.ShowWidgetsStmt)) }) - r.Register(&ast.UpdateWidgetsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execUpdateWidgets(stmt.(*ast.UpdateWidgetsStmt)) + r.Register(&ast.UpdateWidgetsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execUpdateWidgets(stmt.(*ast.UpdateWidgetsStmt)) }) - r.Register(&ast.SelectStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execCatalogQuery(stmt.(*ast.SelectStmt).Query) + r.Register(&ast.SelectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execCatalogQuery(stmt.(*ast.SelectStmt).Query) }) - r.Register(&ast.DescribeStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDescribe(stmt.(*ast.DescribeStmt)) + r.Register(&ast.DescribeStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDescribe(stmt.(*ast.DescribeStmt)) }) - r.Register(&ast.DescribeCatalogTableStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDescribeCatalogTable(stmt.(*ast.DescribeCatalogTableStmt)) + r.Register(&ast.DescribeCatalogTableStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDescribeCatalogTable(stmt.(*ast.DescribeCatalogTableStmt)) }) // NOTE: ShowFeaturesStmt was missing from the original type-switch // (pre-existing bug). Adding it here to fix the dead-code path. - r.Register(&ast.ShowFeaturesStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execShowFeatures(stmt.(*ast.ShowFeaturesStmt)) + r.Register(&ast.ShowFeaturesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execShowFeatures(stmt.(*ast.ShowFeaturesStmt)) }) } func registerStylingHandlers(r *Registry) { - r.Register(&ast.ShowDesignPropertiesStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execShowDesignProperties(stmt.(*ast.ShowDesignPropertiesStmt)) + r.Register(&ast.ShowDesignPropertiesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execShowDesignProperties(stmt.(*ast.ShowDesignPropertiesStmt)) }) - r.Register(&ast.DescribeStylingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDescribeStyling(stmt.(*ast.DescribeStylingStmt)) + r.Register(&ast.DescribeStylingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDescribeStyling(stmt.(*ast.DescribeStylingStmt)) }) - r.Register(&ast.AlterStylingStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterStyling(stmt.(*ast.AlterStylingStmt)) + r.Register(&ast.AlterStylingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterStyling(stmt.(*ast.AlterStylingStmt)) }) } func registerRepositoryHandlers(r *Registry) { - r.Register(&ast.UpdateStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execUpdate() + r.Register(&ast.UpdateStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execUpdate() }) - r.Register(&ast.RefreshStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRefresh() + r.Register(&ast.RefreshStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRefresh() }) - r.Register(&ast.RefreshCatalogStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execRefreshCatalogStmt(stmt.(*ast.RefreshCatalogStmt)) + r.Register(&ast.RefreshCatalogStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execRefreshCatalogStmt(stmt.(*ast.RefreshCatalogStmt)) }) - r.Register(&ast.SearchStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSearch(stmt.(*ast.SearchStmt)) + r.Register(&ast.SearchStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSearch(stmt.(*ast.SearchStmt)) }) } func registerSessionHandlers(r *Registry) { - r.Register(&ast.SetStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSet(stmt.(*ast.SetStmt)) + r.Register(&ast.SetStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSet(stmt.(*ast.SetStmt)) }) - r.Register(&ast.HelpStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execHelp() + r.Register(&ast.HelpStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execHelp() }) - r.Register(&ast.ExitStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execExit() + r.Register(&ast.ExitStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execExit() }) - r.Register(&ast.ExecuteScriptStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execExecuteScript(stmt.(*ast.ExecuteScriptStmt)) + r.Register(&ast.ExecuteScriptStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execExecuteScript(stmt.(*ast.ExecuteScriptStmt)) }) } func registerLintHandlers(r *Registry) { - r.Register(&ast.LintStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execLint(stmt.(*ast.LintStmt)) + r.Register(&ast.LintStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execLint(stmt.(*ast.LintStmt)) }) } func registerAlterPageHandlers(r *Registry) { - r.Register(&ast.AlterPageStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execAlterPage(stmt.(*ast.AlterPageStmt)) + r.Register(&ast.AlterPageStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execAlterPage(stmt.(*ast.AlterPageStmt)) }) } func registerFragmentHandlers(r *Registry) { - r.Register(&ast.DefineFragmentStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execDefineFragment(stmt.(*ast.DefineFragmentStmt)) + r.Register(&ast.DefineFragmentStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execDefineFragment(stmt.(*ast.DefineFragmentStmt)) }) - r.Register(&ast.DescribeFragmentFromStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.describeFragmentFrom(stmt.(*ast.DescribeFragmentFromStmt)) + r.Register(&ast.DescribeFragmentFromStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.describeFragmentFrom(stmt.(*ast.DescribeFragmentFromStmt)) }) } func registerSQLHandlers(r *Registry) { - r.Register(&ast.SQLConnectStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLConnect(stmt.(*ast.SQLConnectStmt)) + r.Register(&ast.SQLConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLConnect(stmt.(*ast.SQLConnectStmt)) }) - r.Register(&ast.SQLDisconnectStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLDisconnect(stmt.(*ast.SQLDisconnectStmt)) + r.Register(&ast.SQLDisconnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLDisconnect(stmt.(*ast.SQLDisconnectStmt)) }) - r.Register(&ast.SQLConnectionsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLConnections() + r.Register(&ast.SQLConnectionsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLConnections() }) - r.Register(&ast.SQLQueryStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLQuery(stmt.(*ast.SQLQueryStmt)) + r.Register(&ast.SQLQueryStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLQuery(stmt.(*ast.SQLQueryStmt)) }) - r.Register(&ast.SQLShowTablesStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLShowTables(stmt.(*ast.SQLShowTablesStmt)) + r.Register(&ast.SQLShowTablesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLShowTables(stmt.(*ast.SQLShowTablesStmt)) }) - r.Register(&ast.SQLShowViewsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLShowViews(stmt.(*ast.SQLShowViewsStmt)) + r.Register(&ast.SQLShowViewsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLShowViews(stmt.(*ast.SQLShowViewsStmt)) }) - r.Register(&ast.SQLShowFunctionsStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLShowFunctions(stmt.(*ast.SQLShowFunctionsStmt)) + r.Register(&ast.SQLShowFunctionsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLShowFunctions(stmt.(*ast.SQLShowFunctionsStmt)) }) - r.Register(&ast.SQLDescribeTableStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLDescribeTable(stmt.(*ast.SQLDescribeTableStmt)) + r.Register(&ast.SQLDescribeTableStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLDescribeTable(stmt.(*ast.SQLDescribeTableStmt)) }) - r.Register(&ast.SQLGenerateConnectorStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execSQLGenerateConnector(stmt.(*ast.SQLGenerateConnectorStmt)) + r.Register(&ast.SQLGenerateConnectorStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execSQLGenerateConnector(stmt.(*ast.SQLGenerateConnectorStmt)) }) } func registerImportHandlers(r *Registry) { - r.Register(&ast.ImportStmt{}, func(e *Executor, stmt ast.Statement) error { - return e.execImport(stmt.(*ast.ImportStmt)) + r.Register(&ast.ImportStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { + return ctx.executor.execImport(stmt.(*ast.ImportStmt)) }) } diff --git a/mdl/executor/registry.go b/mdl/executor/registry.go index 96f3260e..4b3ab747 100644 --- a/mdl/executor/registry.go +++ b/mdl/executor/registry.go @@ -12,7 +12,7 @@ import ( // StmtHandler executes a single statement type. // Implementations receive the concrete statement via type assertion. -type StmtHandler func(e *Executor, stmt ast.Statement) error +type StmtHandler func(ctx *ExecContext, stmt ast.Statement) error // Registry maps AST statement types to their handler functions. type Registry struct { @@ -76,12 +76,12 @@ func (r *Registry) Lookup(stmt ast.Statement) StmtHandler { // Dispatch finds and executes the handler for stmt. Returns an // UnsupportedError if no handler is registered. -func (r *Registry) Dispatch(e *Executor, stmt ast.Statement) error { +func (r *Registry) Dispatch(ctx *ExecContext, stmt ast.Statement) error { h := r.Lookup(stmt) if h == nil { return mdlerrors.NewUnsupported(fmt.Sprintf("unhandled statement type %T", stmt)) } - return h(e, stmt) + return h(ctx, stmt) } // Validate checks that every known AST statement type has a registered diff --git a/mdl/executor/registry_test.go b/mdl/executor/registry_test.go index b2935e8a..4641412f 100644 --- a/mdl/executor/registry_test.go +++ b/mdl/executor/registry_test.go @@ -42,7 +42,7 @@ func TestRegistry_Dispatch_UnknownStatement(t *testing.T) { func TestRegistry_Register_Duplicate_Panics(t *testing.T) { r := emptyRegistry() - handler := func(e *Executor, stmt ast.Statement) error { return nil } + handler := func(ctx *ExecContext, stmt ast.Statement) error { return nil } r.Register(&ast.ConnectStmt{}, handler) @@ -57,7 +57,7 @@ func TestRegistry_Register_Duplicate_Panics(t *testing.T) { func TestRegistry_Dispatch_Success(t *testing.T) { r := emptyRegistry() called := false - r.Register(&ast.ConnectStmt{}, func(e *Executor, stmt ast.Statement) error { + r.Register(&ast.ConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { called = true if _, ok := stmt.(*ast.ConnectStmt); !ok { t.Fatalf("expected *ConnectStmt, got %T", stmt) @@ -77,7 +77,7 @@ func TestRegistry_Dispatch_Success(t *testing.T) { func TestRegistry_Dispatch_HandlerError(t *testing.T) { r := emptyRegistry() sentinel := errors.New("test error") - r.Register(&ast.ConnectStmt{}, func(e *Executor, stmt ast.Statement) error { + r.Register(&ast.ConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return sentinel }) @@ -102,7 +102,7 @@ func TestRegistry_Validate_Empty(t *testing.T) { func TestRegistry_Validate_Complete(t *testing.T) { r := emptyRegistry() - noop := func(e *Executor, stmt ast.Statement) error { return nil } + noop := func(ctx *ExecContext, stmt ast.Statement) error { return nil } r.Register(&ast.ConnectStmt{}, noop) r.Register(&ast.DisconnectStmt{}, noop) @@ -118,7 +118,7 @@ func TestRegistry_Validate_Complete(t *testing.T) { func TestRegistry_Validate_Partial(t *testing.T) { r := emptyRegistry() - noop := func(e *Executor, stmt ast.Statement) error { return nil } + noop := func(ctx *ExecContext, stmt ast.Statement) error { return nil } r.Register(&ast.ConnectStmt{}, noop) knownTypes := []ast.Statement{ @@ -146,7 +146,7 @@ func TestRegistry_HandlerCount(t *testing.T) { if r.HandlerCount() != 0 { t.Fatalf("expected 0, got %d", r.HandlerCount()) } - noop := func(e *Executor, stmt ast.Statement) error { return nil } + noop := func(ctx *ExecContext, stmt ast.Statement) error { return nil } r.Register(&ast.ConnectStmt{}, noop) if r.HandlerCount() != 1 { t.Fatalf("expected 1, got %d", r.HandlerCount()) From c0716f41b0f53feb6b4cfe5efd6c18c9a059222f Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 15:38:54 +0200 Subject: [PATCH 02/16] refactor: migrate connection handlers to free functions Convert execConnect, execDisconnect, execStatus, and reconnect from Executor methods to free functions receiving *ExecContext. Add temporary Executor method wrappers for callers not yet migrated (cmd_misc.go, cmd_catalog.go). Update register_stubs.go to call free functions directly. --- mdl/executor/executor_connect.go | 53 ++++++++++++++++++++++---------- mdl/executor/register_stubs.go | 6 ++-- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/mdl/executor/executor_connect.go b/mdl/executor/executor_connect.go index a0d482bf..140bb25b 100644 --- a/mdl/executor/executor_connect.go +++ b/mdl/executor/executor_connect.go @@ -10,7 +10,8 @@ import ( "github.com/mendixlabs/mxcli/sdk/mpr" ) -func (e *Executor) execConnect(s *ast.ConnectStmt) error { +func execConnect(ctx *ExecContext, s *ast.ConnectStmt) error { + e := ctx.executor if e.writer != nil { e.writer.Close() } @@ -27,18 +28,19 @@ func (e *Executor) execConnect(s *ast.ConnectStmt) error { // Display connection info with version pv := e.reader.ProjectVersion() - if !e.quiet { - fmt.Fprintf(e.output, "Connected to: %s (Mendix %s)\n", s.Path, pv.ProductVersion) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "Connected to: %s (Mendix %s)\n", s.Path, pv.ProductVersion) } - if e.logger != nil { - e.logger.Connect(s.Path, pv.ProductVersion, pv.FormatVersion) + if ctx.Logger != nil { + ctx.Logger.Connect(s.Path, pv.ProductVersion, pv.FormatVersion) } return nil } // reconnect closes the current connection and reopens it. // This is needed when the project file has been modified externally. -func (e *Executor) reconnect() error { +func reconnect(ctx *ExecContext) error { + e := ctx.executor if e.mprPath == "" { return mdlerrors.NewNotConnected() } @@ -60,19 +62,20 @@ func (e *Executor) reconnect() error { return nil } -func (e *Executor) execDisconnect() error { +func execDisconnect(ctx *ExecContext) error { + e := ctx.executor if e.writer == nil { - fmt.Fprintln(e.output, "Not connected") + fmt.Fprintln(ctx.Output, "Not connected") return nil } // Reconcile any pending security changes before closing if err := e.finalizeProgramExecution(); err != nil { - fmt.Fprintf(e.output, "Warning: finalization error: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: finalization error: %v\n", err) } e.writer.Close() - fmt.Fprintf(e.output, "Disconnected from: %s\n", e.mprPath) + fmt.Fprintf(ctx.Output, "Disconnected from: %s\n", e.mprPath) e.writer = nil e.reader = nil e.mprPath = "" @@ -80,22 +83,38 @@ func (e *Executor) execDisconnect() error { return nil } -func (e *Executor) execStatus() error { +// Executor method wrappers — kept during migration for callers not yet +// converted to free functions. Remove once all callers are migrated. + +func (e *Executor) execConnect(s *ast.ConnectStmt) error { + return execConnect(&ExecContext{Output: e.output, Quiet: e.quiet, Logger: e.logger, executor: e}, s) +} + +func (e *Executor) execDisconnect() error { + return execDisconnect(&ExecContext{Output: e.output, executor: e}) +} + +func (e *Executor) reconnect() error { + return reconnect(&ExecContext{executor: e}) +} + +func execStatus(ctx *ExecContext) error { + e := ctx.executor if e.writer == nil { - fmt.Fprintln(e.output, "Status: Not connected") + fmt.Fprintln(ctx.Output, "Status: Not connected") return nil } pv := e.reader.ProjectVersion() - fmt.Fprintf(e.output, "Status: Connected\n") - fmt.Fprintf(e.output, "Project: %s\n", e.mprPath) - fmt.Fprintf(e.output, "Mendix Version: %s\n", pv.ProductVersion) - fmt.Fprintf(e.output, "MPR Format: v%d\n", pv.FormatVersion) + fmt.Fprintf(ctx.Output, "Status: Connected\n") + fmt.Fprintf(ctx.Output, "Project: %s\n", e.mprPath) + fmt.Fprintf(ctx.Output, "Mendix Version: %s\n", pv.ProductVersion) + fmt.Fprintf(ctx.Output, "MPR Format: v%d\n", pv.FormatVersion) // Show module count modules, err := e.reader.ListModules() if err == nil { - fmt.Fprintf(e.output, "Modules: %d\n", len(modules)) + fmt.Fprintf(ctx.Output, "Modules: %d\n", len(modules)) } return nil diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 0a2cf0e9..e2ae33eb 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -11,13 +11,13 @@ import "github.com/mendixlabs/mxcli/mdl/ast" func registerConnectionHandlers(r *Registry) { r.Register(&ast.ConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execConnect(stmt.(*ast.ConnectStmt)) + return execConnect(ctx, stmt.(*ast.ConnectStmt)) }) r.Register(&ast.DisconnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDisconnect() + return execDisconnect(ctx) }) r.Register(&ast.StatusStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execStatus() + return execStatus(ctx) }) } From 5aca96e2b1513c87794a21315faa92b844cbb3b4 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 15:41:50 +0200 Subject: [PATCH 03/16] refactor: migrate module handlers to free functions Convert execCreateModule and execDropModule from Executor methods to free functions receiving *ExecContext. Add temporary method wrapper for execCreateModule (called from helpers.go auto-create). Leave showModules, describeModule, and helper methods as Executor methods for now. --- mdl/executor/cmd_modules.go | 51 +++++++++++++++++++------------- mdl/executor/executor_connect.go | 7 +++-- mdl/executor/register_stubs.go | 4 +-- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/mdl/executor/cmd_modules.go b/mdl/executor/cmd_modules.go index b43b5b1e..63e43849 100644 --- a/mdl/executor/cmd_modules.go +++ b/mdl/executor/cmd_modules.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,8 @@ import ( ) // execCreateModule handles CREATE MODULE statements. -func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { +func execCreateModule(ctx *ExecContext, s *ast.CreateModuleStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -28,7 +30,7 @@ func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { for _, m := range modules { if m.Name == s.Name { - fmt.Fprintf(e.output, "Module '%s' already exists\n", s.Name) + fmt.Fprintf(ctx.Output, "Module '%s' already exists\n", s.Name) return nil } } @@ -45,7 +47,7 @@ func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { // Invalidate cache so new module is visible e.invalidateModuleCache() - fmt.Fprintf(e.output, "Created module: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created module: %s\n", s.Name) return nil } @@ -58,7 +60,8 @@ func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { // - Pages // - Snippets // - Constants -func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { +func execDropModule(ctx *ExecContext, s *ast.DropModuleStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -92,7 +95,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, enum := range enums { if moduleContainers[enum.ContainerID] { if err := e.writer.DeleteEnumeration(enum.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete enumeration %s: %v\n", enum.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete enumeration %s: %v\n", enum.Name, err) } else { nEnums++ } @@ -107,7 +110,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { // Delete all associations in this domain model first (they reference entities) for _, assoc := range dm.Associations { if err := e.writer.DeleteAssociation(dm.ID, assoc.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete association %s: %v\n", assoc.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete association %s: %v\n", assoc.Name, err) } else { nAssocs++ } @@ -115,7 +118,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { // Delete all entities in this domain model for _, entity := range dm.Entities { if err := e.writer.DeleteEntity(dm.ID, entity.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete entity %s: %v\n", entity.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete entity %s: %v\n", entity.Name, err) } else { nEntities++ } @@ -129,7 +132,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, mf := range mfs { if moduleContainers[mf.ContainerID] { if err := e.writer.DeleteMicroflow(mf.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete microflow %s: %v\n", mf.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete microflow %s: %v\n", mf.Name, err) } else { nMicroflows++ } @@ -142,7 +145,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, nf := range nfs { if moduleContainers[nf.ContainerID] { if err := e.writer.DeleteNanoflow(nf.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete nanoflow %s: %v\n", nf.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete nanoflow %s: %v\n", nf.Name, err) } else { nNanoflows++ } @@ -155,7 +158,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, page := range pages { if moduleContainers[page.ContainerID] { if err := e.writer.DeletePage(page.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete page %s: %v\n", page.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete page %s: %v\n", page.Name, err) } else { nPages++ } @@ -168,7 +171,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, snippet := range snippets { if moduleContainers[snippet.ContainerID] { if err := e.writer.DeleteSnippet(snippet.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete snippet %s: %v\n", snippet.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete snippet %s: %v\n", snippet.Name, err) } else { nSnippets++ } @@ -181,7 +184,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, c := range constants { if moduleContainers[c.ContainerID] { if err := e.writer.DeleteConstant(c.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete constant %s: %v\n", c.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete constant %s: %v\n", c.Name, err) } else { nConstants++ } @@ -194,7 +197,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, l := range layouts { if moduleContainers[l.ContainerID] { if err := e.writer.DeleteLayout(l.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete layout %s: %v\n", l.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete layout %s: %v\n", l.Name, err) } else { nLayouts++ } @@ -207,7 +210,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, ja := range jas { if moduleContainers[ja.ContainerID] { if err := e.writer.DeleteJavaAction(ja.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete Java action %s: %v\n", ja.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete Java action %s: %v\n", ja.Name, err) } else { nJavaActions++ } @@ -220,7 +223,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, svc := range services { if moduleContainers[svc.ContainerID] { if err := e.writer.DeleteBusinessEventService(svc.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete business event service %s: %v\n", svc.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete business event service %s: %v\n", svc.Name, err) } else { nBizEvents++ } @@ -233,7 +236,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, conn := range conns { if moduleContainers[conn.ContainerID] { if err := e.writer.DeleteDatabaseConnection(conn.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete database connection %s: %v\n", conn.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete database connection %s: %v\n", conn.Name, err) } else { nDbConns++ } @@ -246,7 +249,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, svc := range services { if moduleContainers[svc.ContainerID] { if err := e.writer.DeleteConsumedODataService(svc.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete OData client %s: %v\n", svc.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete OData client %s: %v\n", svc.Name, err) } else { nServices++ } @@ -259,7 +262,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, svc := range services { if moduleContainers[svc.ContainerID] { if err := e.writer.DeletePublishedODataService(svc.ID); err != nil { - fmt.Fprintf(e.output, "Warning: failed to delete OData service %s: %v\n", svc.Name, err) + fmt.Fprintf(ctx.Output, "Warning: failed to delete OData service %s: %v\n", svc.Name, err) } else { nServices++ } @@ -273,7 +276,7 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { for _, mr := range ms.ModuleRoles { qualifiedRole := s.Name + "." + mr.Name if n, err := e.writer.RemoveModuleRoleFromAllUserRoles(ps.ID, qualifiedRole); err == nil && n > 0 { - fmt.Fprintf(e.output, "Removed %s from %d user role(s)\n", qualifiedRole, n) + fmt.Fprintf(ctx.Output, "Removed %s from %d user role(s)\n", qualifiedRole, n) } } } @@ -327,13 +330,19 @@ func (e *Executor) execDropModule(s *ast.DropModuleStmt) error { } if len(parts) > 0 { - fmt.Fprintf(e.output, "Dropped module: %s (%s)\n", s.Name, strings.Join(parts, ", ")) + fmt.Fprintf(ctx.Output, "Dropped module: %s (%s)\n", s.Name, strings.Join(parts, ", ")) } else { - fmt.Fprintf(e.output, "Dropped module: %s (empty)\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped module: %s (empty)\n", s.Name) } return nil } +// Executor method wrapper — kept during migration for callers not yet +// converted to free functions (helpers.go). Remove once all callers are migrated. +func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { + return execCreateModule(e.newExecContext(context.Background()), s) +} + // getModuleContainers returns a set of all container IDs that belong to a module // (including nested folders). func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { diff --git a/mdl/executor/executor_connect.go b/mdl/executor/executor_connect.go index 140bb25b..f79fc314 100644 --- a/mdl/executor/executor_connect.go +++ b/mdl/executor/executor_connect.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -87,15 +88,15 @@ func execDisconnect(ctx *ExecContext) error { // converted to free functions. Remove once all callers are migrated. func (e *Executor) execConnect(s *ast.ConnectStmt) error { - return execConnect(&ExecContext{Output: e.output, Quiet: e.quiet, Logger: e.logger, executor: e}, s) + return execConnect(e.newExecContext(context.Background()), s) } func (e *Executor) execDisconnect() error { - return execDisconnect(&ExecContext{Output: e.output, executor: e}) + return execDisconnect(e.newExecContext(context.Background())) } func (e *Executor) reconnect() error { - return reconnect(&ExecContext{executor: e}) + return reconnect(e.newExecContext(context.Background())) } func execStatus(ctx *ExecContext) error { diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index e2ae33eb..22a15b9a 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -23,10 +23,10 @@ func registerConnectionHandlers(r *Registry) { func registerModuleHandlers(r *Registry) { r.Register(&ast.CreateModuleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateModule(stmt.(*ast.CreateModuleStmt)) + return execCreateModule(ctx, stmt.(*ast.CreateModuleStmt)) }) r.Register(&ast.DropModuleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropModule(stmt.(*ast.DropModuleStmt)) + return execDropModule(ctx, stmt.(*ast.DropModuleStmt)) }) } From acb3c3d3e611b19f58db766db467d42e84a76cab Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 15:49:36 +0200 Subject: [PATCH 04/16] refactor: migrate enumeration and constant handlers to free functions --- mdl/executor/cmd_constants.go | 73 +++++++++++++++++++++++--------- mdl/executor/cmd_enumerations.go | 54 +++++++++++++++++------ mdl/executor/register_stubs.go | 10 ++--- 3 files changed, 98 insertions(+), 39 deletions(-) diff --git a/mdl/executor/cmd_constants.go b/mdl/executor/cmd_constants.go index b414333a..dd41ca61 100644 --- a/mdl/executor/cmd_constants.go +++ b/mdl/executor/cmd_constants.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -13,7 +14,9 @@ import ( ) // showConstants handles SHOW CONSTANTS command. -func (e *Executor) showConstants(moduleName string) error { +func showConstants(ctx *ExecContext, moduleName string) error { + e := ctx.executor + constants, err := e.reader.ListConstants() if err != nil { return mdlerrors.NewBackend("list constants", err) @@ -60,7 +63,7 @@ func (e *Executor) showConstants(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No constants found.") + fmt.Fprintln(ctx.Output, "No constants found.") return nil } @@ -79,8 +82,15 @@ func (e *Executor) showConstants(moduleName string) error { return e.writeResult(result) } +// showConstants is an Executor method wrapper for callers not yet migrated. +func (e *Executor) showConstants(moduleName string) error { + return showConstants(e.newExecContext(context.Background()), moduleName) +} + // describeConstant handles DESCRIBE CONSTANT command. -func (e *Executor) describeConstant(name ast.QualifiedName) error { +func describeConstant(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + constants, err := e.reader.ListConstants() if err != nil { return mdlerrors.NewBackend("list constants", err) @@ -97,45 +107,57 @@ func (e *Executor) describeConstant(name ast.QualifiedName) error { modID := h.FindModuleID(c.ContainerID) modName := h.GetModuleName(modID) if strings.EqualFold(modName, name.Module) && strings.EqualFold(c.Name, name.Name) { - return e.outputConstantMDL(c, modName) + return outputConstantMDL(ctx, c, modName) } } return mdlerrors.NewNotFound("constant", name.String()) } +// describeConstant is an Executor method wrapper for callers not yet migrated. +func (e *Executor) describeConstant(name ast.QualifiedName) error { + return describeConstant(e.newExecContext(context.Background()), name) +} + // outputConstantMDL outputs a constant definition in MDL format. -func (e *Executor) outputConstantMDL(c *model.Constant, moduleName string) error { +func outputConstantMDL(ctx *ExecContext, c *model.Constant, moduleName string) error { + e := ctx.executor + // Format default value based on type defaultValueStr := formatDefaultValue(c.Type, c.DefaultValue) - fmt.Fprintf(e.output, "CREATE OR MODIFY CONSTANT %s.%s\n", moduleName, c.Name) - fmt.Fprintf(e.output, " TYPE %s\n", formatConstantTypeForMDL(c.Type)) - fmt.Fprintf(e.output, " DEFAULT %s", defaultValueStr) + fmt.Fprintf(ctx.Output, "CREATE OR MODIFY CONSTANT %s.%s\n", moduleName, c.Name) + fmt.Fprintf(ctx.Output, " TYPE %s\n", formatConstantTypeForMDL(c.Type)) + fmt.Fprintf(ctx.Output, " DEFAULT %s", defaultValueStr) // Add folder if present h, _ := e.getHierarchy() if h != nil { if folderPath := h.BuildFolderPath(c.ContainerID); folderPath != "" { - fmt.Fprintf(e.output, "\n FOLDER '%s'", folderPath) + fmt.Fprintf(ctx.Output, "\n FOLDER '%s'", folderPath) } } // Add options if present if c.Documentation != "" { escaped := strings.ReplaceAll(c.Documentation, "'", "''") - fmt.Fprintf(e.output, "\n COMMENT '%s'", escaped) + fmt.Fprintf(ctx.Output, "\n COMMENT '%s'", escaped) } if c.ExposedToClient { - fmt.Fprintf(e.output, "\n EXPOSED TO CLIENT") + fmt.Fprintf(ctx.Output, "\n EXPOSED TO CLIENT") } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") return nil } +// outputConstantMDL is an Executor method wrapper for callers not yet migrated. +func (e *Executor) outputConstantMDL(c *model.Constant, moduleName string) error { + return outputConstantMDL(e.newExecContext(context.Background()), c, moduleName) +} + // formatConstantType returns a human-readable type string. func formatConstantType(dt model.ConstantDataType) string { switch dt.Kind { @@ -247,7 +269,9 @@ func formatDefaultValue(dt model.ConstantDataType, value string) string { } // createConstant handles CREATE CONSTANT command. -func (e *Executor) createConstant(stmt *ast.CreateConstantStmt) error { +func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -294,7 +318,7 @@ func (e *Executor) createConstant(stmt *ast.CreateConstantStmt) error { return mdlerrors.NewBackend("update constant", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified constant: %s.%s\n", modName, c.Name) + fmt.Fprintf(ctx.Output, "Modified constant: %s.%s\n", modName, c.Name) return nil } return mdlerrors.NewAlreadyExistsMsg("constant", modName+"."+c.Name, fmt.Sprintf("constant already exists: %s.%s (use CREATE OR MODIFY to update)", modName, c.Name)) @@ -330,13 +354,15 @@ func (e *Executor) createConstant(stmt *ast.CreateConstantStmt) error { return mdlerrors.NewBackend("create constant", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created constant: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + fmt.Fprintf(ctx.Output, "Created constant: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } // showConstantValues handles SHOW CONSTANT VALUES command. // Displays one row per constant per configuration for easy comparison. -func (e *Executor) showConstantValues(moduleName string) error { +func showConstantValues(ctx *ExecContext, moduleName string) error { + e := ctx.executor + constants, err := e.reader.ListConstants() if err != nil { return mdlerrors.NewBackend("list constants", err) @@ -373,7 +399,7 @@ func (e *Executor) showConstantValues(moduleName string) error { } if len(consts) == 0 { - fmt.Fprintln(e.output, "No constants found.") + fmt.Fprintln(ctx.Output, "No constants found.") return nil } @@ -429,8 +455,15 @@ func (e *Executor) showConstantValues(moduleName string) error { return e.writeResult(result) } +// showConstantValues is an Executor method wrapper for callers not yet migrated. +func (e *Executor) showConstantValues(moduleName string) error { + return showConstantValues(e.newExecContext(context.Background()), moduleName) +} + // dropConstant handles DROP CONSTANT command. -func (e *Executor) dropConstant(stmt *ast.DropConstantStmt) error { +func dropConstant(ctx *ExecContext, stmt *ast.DropConstantStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -455,7 +488,7 @@ func (e *Executor) dropConstant(stmt *ast.DropConstantStmt) error { return mdlerrors.NewBackend("drop constant", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped constant: %s.%s\n", modName, c.Name) + fmt.Fprintf(ctx.Output, "Dropped constant: %s.%s\n", modName, c.Name) return nil } } diff --git a/mdl/executor/cmd_enumerations.go b/mdl/executor/cmd_enumerations.go index 7760a1e9..9b620a87 100644 --- a/mdl/executor/cmd_enumerations.go +++ b/mdl/executor/cmd_enumerations.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,9 @@ import ( ) // execCreateEnumeration handles CREATE ENUMERATION statements. -func (e *Executor) execCreateEnumeration(s *ast.CreateEnumerationStmt) error { +func execCreateEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -37,7 +40,7 @@ func (e *Executor) execCreateEnumeration(s *ast.CreateEnumerationStmt) error { } // Check if enumeration already exists - existingEnum := e.findEnumeration(s.Name.Module, s.Name.Name) + existingEnum := findEnumeration(ctx, s.Name.Module, s.Name.Name) if existingEnum != nil && !s.CreateOrModify { return mdlerrors.NewAlreadyExistsMsg("enumeration", s.Name.Module+"."+s.Name.Name, fmt.Sprintf("enumeration already exists: %s.%s (use CREATE OR MODIFY to update)", s.Name.Module, s.Name.Name)) } @@ -75,12 +78,14 @@ func (e *Executor) execCreateEnumeration(s *ast.CreateEnumerationStmt) error { // Invalidate hierarchy cache so the new enumeration's container is visible e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created enumeration: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created enumeration: %s\n", s.Name) return nil } // findEnumeration finds an enumeration by module and name. -func (e *Executor) findEnumeration(moduleName, enumName string) *model.Enumeration { +func findEnumeration(ctx *ExecContext, moduleName, enumName string) *model.Enumeration { + e := ctx.executor + enums, err := e.reader.ListEnumerations() if err != nil { return nil @@ -101,14 +106,21 @@ func (e *Executor) findEnumeration(moduleName, enumName string) *model.Enumerati return nil } +// findEnumeration is an Executor method wrapper for callers not yet migrated. +func (e *Executor) findEnumeration(moduleName, enumName string) *model.Enumeration { + return findEnumeration(e.newExecContext(context.Background()), moduleName, enumName) +} + // execAlterEnumeration handles ALTER ENUMERATION statements. -func (e *Executor) execAlterEnumeration(s *ast.AlterEnumerationStmt) error { +func execAlterEnumeration(ctx *ExecContext, s *ast.AlterEnumerationStmt) error { // TODO: Implement ALTER ENUMERATION return mdlerrors.NewUnsupported("ALTER ENUMERATION not yet implemented") } // execDropEnumeration handles DROP ENUMERATION statements. -func (e *Executor) execDropEnumeration(s *ast.DropEnumerationStmt) error { +func execDropEnumeration(ctx *ExecContext, s *ast.DropEnumerationStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -127,7 +139,7 @@ func (e *Executor) execDropEnumeration(s *ast.DropEnumerationStmt) error { if err := e.writer.DeleteEnumeration(enum.ID); err != nil { return mdlerrors.NewBackend("delete enumeration", err) } - fmt.Fprintf(e.output, "Dropped enumeration: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped enumeration: %s\n", s.Name) return nil } } @@ -137,7 +149,9 @@ func (e *Executor) execDropEnumeration(s *ast.DropEnumerationStmt) error { } // showEnumerations handles SHOW ENUMERATIONS command. -func (e *Executor) showEnumerations(moduleName string) error { +func showEnumerations(ctx *ExecContext, moduleName string) error { + e := ctx.executor + enums, err := e.reader.ListEnumerations() if err != nil { return mdlerrors.NewBackend("list enumerations", err) @@ -186,8 +200,15 @@ func (e *Executor) showEnumerations(moduleName string) error { return e.writeResult(result) } +// showEnumerations is an Executor method wrapper for callers not yet migrated. +func (e *Executor) showEnumerations(moduleName string) error { + return showEnumerations(e.newExecContext(context.Background()), moduleName) +} + // describeEnumeration handles DESCRIBE ENUMERATION command. -func (e *Executor) describeEnumeration(name ast.QualifiedName) error { +func describeEnumeration(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + enums, err := e.reader.ListEnumerations() if err != nil { return mdlerrors.NewBackend("list enumerations", err) @@ -204,10 +225,10 @@ func (e *Executor) describeEnumeration(name ast.QualifiedName) error { if enum.Name == name.Name && (name.Module == "" || modName == name.Module) { // Output JavaDoc documentation if present if enum.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", enum.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", enum.Documentation) } - fmt.Fprintf(e.output, "CREATE OR MODIFY ENUMERATION %s.%s (\n", modName, enum.Name) + fmt.Fprintf(ctx.Output, "CREATE OR MODIFY ENUMERATION %s.%s (\n", modName, enum.Name) for i, v := range enum.Values { comma := "," if i == len(enum.Values)-1 { @@ -217,10 +238,10 @@ func (e *Executor) describeEnumeration(name ast.QualifiedName) error { if v.Caption != nil { caption = v.Caption.GetTranslation("en_US") } - fmt.Fprintf(e.output, " %s '%s'%s\n", v.Name, caption, comma) + fmt.Fprintf(ctx.Output, " %s '%s'%s\n", v.Name, caption, comma) } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } } @@ -228,6 +249,11 @@ func (e *Executor) describeEnumeration(name ast.QualifiedName) error { return mdlerrors.NewNotFound("enumeration", name.String()) } +// describeEnumeration is an Executor method wrapper for callers not yet migrated. +func (e *Executor) describeEnumeration(name ast.QualifiedName) error { + return describeEnumeration(e.newExecContext(context.Background()), name) +} + // mendixReservedWords contains words that cannot be used as enumeration value names. // These are Java reserved words plus Mendix-specific reserved identifiers. // Using any of these triggers CE7247: "The name 'X' is a reserved word." diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 22a15b9a..655b7bfd 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -32,22 +32,22 @@ func registerModuleHandlers(r *Registry) { func registerEnumerationHandlers(r *Registry) { r.Register(&ast.CreateEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateEnumeration(stmt.(*ast.CreateEnumerationStmt)) + return execCreateEnumeration(ctx, stmt.(*ast.CreateEnumerationStmt)) }) r.Register(&ast.AlterEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterEnumeration(stmt.(*ast.AlterEnumerationStmt)) + return execAlterEnumeration(ctx, stmt.(*ast.AlterEnumerationStmt)) }) r.Register(&ast.DropEnumerationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropEnumeration(stmt.(*ast.DropEnumerationStmt)) + return execDropEnumeration(ctx, stmt.(*ast.DropEnumerationStmt)) }) } func registerConstantHandlers(r *Registry) { r.Register(&ast.CreateConstantStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createConstant(stmt.(*ast.CreateConstantStmt)) + return createConstant(ctx, stmt.(*ast.CreateConstantStmt)) }) r.Register(&ast.DropConstantStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropConstant(stmt.(*ast.DropConstantStmt)) + return dropConstant(ctx, stmt.(*ast.DropConstantStmt)) }) } From f785fcbdcc2890b693f4f4cb1a89be1fe1353457 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 15:58:37 +0200 Subject: [PATCH 05/16] refactor: migrate entity, association, and domain model handlers to free functions --- mdl/executor/cmd_associations.go | 99 ++++++++++++++++--------- mdl/executor/cmd_domainmodel_elk.go | 50 ++++++++----- mdl/executor/cmd_entities.go | 96 +++++++++++++++---------- mdl/executor/cmd_entities_access.go | 36 +++++++--- mdl/executor/cmd_entities_describe.go | 100 +++++++++++++++++--------- mdl/executor/register_stubs.go | 14 ++-- 6 files changed, 260 insertions(+), 135 deletions(-) diff --git a/mdl/executor/cmd_associations.go b/mdl/executor/cmd_associations.go index b005b836..3336ea2d 100644 --- a/mdl/executor/cmd_associations.go +++ b/mdl/executor/cmd_associations.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,8 @@ import ( ) // execCreateAssociation handles CREATE ASSOCIATION statements. -func (e *Executor) execCreateAssociation(s *ast.CreateAssociationStmt) error { +func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -133,17 +135,18 @@ func (e *Executor) execCreateAssociation(s *ast.CreateAssociationStmt) error { // in this DM need MemberAccess entries for the new association (CE0066). if freshDM, err := e.reader.GetDomainModel(module.ID); err == nil { if count, err := e.writer.ReconcileMemberAccesses(freshDM.ID, module.Name); err == nil && count > 0 { - fmt.Fprintf(e.output, "Reconciled %d access rule(s) for new association\n", count) + fmt.Fprintf(ctx.Output, "Reconciled %d access rule(s) for new association\n", count) } } e.trackModifiedDomainModel(module.ID, module.Name) - fmt.Fprintf(e.output, "Created association: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created association: %s\n", s.Name) return nil } // execAlterAssociation handles ALTER ASSOCIATION statements. -func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { +func execAlterAssociation(ctx *ExecContext, s *ast.AlterAssociationStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -176,7 +179,7 @@ func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { if err := e.writer.UpdateDomainModel(dm); err != nil { return mdlerrors.NewBackend("update association", err) } - fmt.Fprintf(e.output, "Altered association: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Altered association: %s\n", s.Name) return nil } } @@ -199,7 +202,7 @@ func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { if err := e.writer.UpdateDomainModel(dm); err != nil { return mdlerrors.NewBackend("update cross-module association", err) } - fmt.Fprintf(e.output, "Altered association: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Altered association: %s\n", s.Name) return nil } } @@ -208,7 +211,8 @@ func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { } // execDropAssociation handles DROP ASSOCIATION statements. -func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { +func execDropAssociation(ctx *ExecContext, s *ast.DropAssociationStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -229,7 +233,7 @@ func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { if err := e.writer.DeleteAssociation(dm.ID, assoc.ID); err != nil { return mdlerrors.NewBackend("delete association", err) } - fmt.Fprintf(e.output, "Dropped association: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped association: %s\n", s.Name) return nil } } @@ -238,7 +242,7 @@ func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { if err := e.writer.DeleteCrossAssociation(dm.ID, ca.ID); err != nil { return mdlerrors.NewBackend("delete cross-module association", err) } - fmt.Fprintf(e.output, "Dropped cross-module association: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped cross-module association: %s\n", s.Name) return nil } } @@ -247,7 +251,8 @@ func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { } // showAssociations handles SHOW ASSOCIATIONS command. -func (e *Executor) showAssociations(moduleName string) error { +func showAssociations(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Build module ID -> name map (single query) modules, err := e.reader.ListModules() if err != nil { @@ -333,7 +338,8 @@ func (e *Executor) showAssociations(moduleName string) error { } // showAssociation handles SHOW ASSOCIATION command. -func (e *Executor) showAssociation(name *ast.QualifiedName) error { +func showAssociation(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("association name required") } @@ -350,20 +356,20 @@ func (e *Executor) showAssociation(name *ast.QualifiedName) error { for _, assoc := range dm.Associations { if assoc.Name == name.Name { - fmt.Fprintf(e.output, "Association: %s.%s\n", module.Name, assoc.Name) - fmt.Fprintf(e.output, " Type: %s\n", assoc.Type) - fmt.Fprintf(e.output, " Owner: %s\n", assoc.Owner) - fmt.Fprintf(e.output, " Storage: %s\n", assoc.StorageFormat) + fmt.Fprintf(ctx.Output, "Association: %s.%s\n", module.Name, assoc.Name) + fmt.Fprintf(ctx.Output, " Type: %s\n", assoc.Type) + fmt.Fprintf(ctx.Output, " Owner: %s\n", assoc.Owner) + fmt.Fprintf(ctx.Output, " Storage: %s\n", assoc.StorageFormat) return nil } } for _, ca := range dm.CrossAssociations { if ca.Name == name.Name { - fmt.Fprintf(e.output, "Association: %s.%s (cross-module)\n", module.Name, ca.Name) - fmt.Fprintf(e.output, " Type: %s\n", ca.Type) - fmt.Fprintf(e.output, " Owner: %s\n", ca.Owner) - fmt.Fprintf(e.output, " Storage: %s\n", ca.StorageFormat) - fmt.Fprintf(e.output, " Child: %s\n", ca.ChildRef) + fmt.Fprintf(ctx.Output, "Association: %s.%s (cross-module)\n", module.Name, ca.Name) + fmt.Fprintf(ctx.Output, " Type: %s\n", ca.Type) + fmt.Fprintf(ctx.Output, " Owner: %s\n", ca.Owner) + fmt.Fprintf(ctx.Output, " Storage: %s\n", ca.StorageFormat) + fmt.Fprintf(ctx.Output, " Child: %s\n", ca.ChildRef) return nil } } @@ -372,7 +378,8 @@ func (e *Executor) showAssociation(name *ast.QualifiedName) error { } // describeAssociation handles DESCRIBE ASSOCIATION command. -func (e *Executor) describeAssociation(name ast.QualifiedName) error { +func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor module, err := e.findModule(name.Module) if err != nil { return err @@ -406,17 +413,17 @@ func (e *Executor) describeAssociation(name ast.QualifiedName) error { if assocType == domainmodel.AssociationTypeReferenceSet { typeName = "ReferenceSet" } - fmt.Fprintf(e.output, "TYPE %s\n", typeName) + fmt.Fprintf(ctx.Output, "TYPE %s\n", typeName) owner := "Default" if assocOwner == domainmodel.AssociationOwnerBoth { owner = "Both" } - fmt.Fprintf(e.output, "OWNER %s\n", owner) + fmt.Fprintf(ctx.Output, "OWNER %s\n", owner) // Only output STORAGE when it's not the default (Table) if storageFormat == domainmodel.StorageFormatColumn { - fmt.Fprintf(e.output, "STORAGE COLUMN\n") + fmt.Fprintf(ctx.Output, "STORAGE COLUMN\n") } deleteBehavior := "DELETE_BUT_KEEP_REFERENCES" @@ -430,7 +437,7 @@ func (e *Executor) describeAssociation(name ast.QualifiedName) error { deleteBehavior = "DELETE_BUT_KEEP_REFERENCES" } } - fmt.Fprintf(e.output, "DELETE_BEHAVIOR %s;\n", deleteBehavior) + fmt.Fprintf(ctx.Output, "DELETE_BEHAVIOR %s;\n", deleteBehavior) } for _, assoc := range dm.Associations { @@ -439,13 +446,13 @@ func (e *Executor) describeAssociation(name ast.QualifiedName) error { toEntity := entityNames[assoc.ChildID] if assoc.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", assoc.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", assoc.Documentation) } - fmt.Fprintf(e.output, "CREATE ASSOCIATION %s.%s\n", module.Name, assoc.Name) - fmt.Fprintf(e.output, "FROM %s TO %s\n", fromEntity, toEntity) + fmt.Fprintf(ctx.Output, "CREATE ASSOCIATION %s.%s\n", module.Name, assoc.Name) + fmt.Fprintf(ctx.Output, "FROM %s TO %s\n", fromEntity, toEntity) formatAssocDetails(assoc.Type, assoc.Owner, assoc.StorageFormat, assoc.ChildDeleteBehavior) - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } } @@ -457,16 +464,42 @@ func (e *Executor) describeAssociation(name ast.QualifiedName) error { } if ca.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", ca.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", ca.Documentation) } - fmt.Fprintf(e.output, "CREATE ASSOCIATION %s.%s\n", module.Name, ca.Name) - fmt.Fprintf(e.output, "FROM %s TO %s\n", fromEntity, ca.ChildRef) + fmt.Fprintf(ctx.Output, "CREATE ASSOCIATION %s.%s\n", module.Name, ca.Name) + fmt.Fprintf(ctx.Output, "FROM %s TO %s\n", fromEntity, ca.ChildRef) formatAssocDetails(ca.Type, ca.Owner, ca.StorageFormat, ca.ChildDeleteBehavior) - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } } return mdlerrors.NewNotFound("association", name.String()) } + +// --- Executor method wrappers for callers not yet migrated --- + +func (e *Executor) execCreateAssociation(s *ast.CreateAssociationStmt) error { + return execCreateAssociation(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { + return execAlterAssociation(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { + return execDropAssociation(e.newExecContext(context.Background()), s) +} + +func (e *Executor) showAssociations(moduleName string) error { + return showAssociations(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) showAssociation(name *ast.QualifiedName) error { + return showAssociation(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeAssociation(name ast.QualifiedName) error { + return describeAssociation(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_domainmodel_elk.go b/mdl/executor/cmd_domainmodel_elk.go index 9ae04c22..95c2e3da 100644 --- a/mdl/executor/cmd_domainmodel_elk.go +++ b/mdl/executor/cmd_domainmodel_elk.go @@ -3,6 +3,7 @@ package executor import ( + "context" "encoding/json" "fmt" "strings" @@ -64,16 +65,17 @@ const ( elkMinWidth = 100.0 ) -// DomainModelELK generates a JSON graph of a module's domain model for rendering with ELK.js. -// If name contains a dot (e.g. "Module.Entity"), it delegates to EntityFocusELK for a focused view. -func (e *Executor) DomainModelELK(name string) error { +// domainModelELK generates a JSON graph of a module's domain model for rendering with ELK.js. +// If name contains a dot (e.g. "Module.Entity"), it delegates to entityFocusELK for a focused view. +func domainModelELK(ctx *ExecContext, name string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } // If name is qualified (Module.Entity), render focused entity diagram if strings.Contains(name, ".") { - return e.EntityFocusELK(name) + return entityFocusELK(ctx, name) } moduleName := name @@ -87,7 +89,7 @@ func (e *Executor) DomainModelELK(name string) error { return mdlerrors.NewBackend("get domain model", err) } - allEntityNames, _ := e.buildAllEntityNames() + allEntityNames, _ := buildAllEntityNames(ctx) // Track which entity IDs are in the current module moduleEntityIDs := make(map[model.ID]bool) @@ -138,9 +140,9 @@ func (e *Executor) DomainModelELK(name string) error { } // Generate MDL source with source map - mdlSource, sourceMap := e.buildDomainModelMdlSource(dm.Entities, moduleName) + mdlSource, sourceMap := buildDomainModelMdlSource(ctx, dm.Entities, moduleName) - return e.emitDomainModelELK(domainModelELKData{ + return emitDomainModelELK(ctx, domainModelELKData{ Format: "elk", Type: "domainmodel", ModuleName: moduleName, @@ -152,9 +154,10 @@ func (e *Executor) DomainModelELK(name string) error { }) } -// EntityFocusELK generates a focused ELK diagram showing only the selected entity +// entityFocusELK generates a focused ELK diagram showing only the selected entity // and entities directly connected to it via associations or generalization. -func (e *Executor) EntityFocusELK(qualifiedName string) error { +func entityFocusELK(ctx *ExecContext, qualifiedName string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -192,7 +195,7 @@ func (e *Executor) EntityFocusELK(qualifiedName string) error { return e.OqlQueryPlanELK(qualifiedName, focusEntity) } - allEntityNames, _ := e.buildAllEntityNames() + allEntityNames, _ := buildAllEntityNames(ctx) // Build map of all entities in this module by ID for quick lookup moduleEntitiesByID := make(map[model.ID]*domainmodel.Entity) @@ -302,9 +305,9 @@ func (e *Executor) EntityFocusELK(qualifiedName string) error { } // Generate MDL source with source map for focus entity - mdlSource, sourceMap := e.buildDomainModelMdlSource([]*domainmodel.Entity{focusEntity}, moduleName) + mdlSource, sourceMap := buildDomainModelMdlSource(ctx, []*domainmodel.Entity{focusEntity}, moduleName) - return e.emitDomainModelELK(domainModelELKData{ + return emitDomainModelELK(ctx, domainModelELKData{ Format: "elk", Type: "domainmodel", ModuleName: moduleName, @@ -321,7 +324,8 @@ func (e *Executor) EntityFocusELK(qualifiedName string) error { // buildAllEntityNames loads all entities across all modules. // Returns ID -> "Module.Entity" map and ID -> module name map. -func (e *Executor) buildAllEntityNames() (map[model.ID]string, map[model.ID]string) { +func buildAllEntityNames(ctx *ExecContext) (map[model.ID]string, map[model.ID]string) { + e := ctx.executor allEntityNames := make(map[model.ID]string) allEntityModules := make(map[model.ID]string) h, err := e.getHierarchy() @@ -449,14 +453,14 @@ func assocTypeStr(t domainmodel.AssociationType) string { // buildDomainModelMdlSource generates combined MDL source for a set of entities // and returns the source string and a source map mapping entity ELK IDs to line ranges. -func (e *Executor) buildDomainModelMdlSource(entities []*domainmodel.Entity, moduleName string) (string, map[string]elkSourceRange) { +func buildDomainModelMdlSource(ctx *ExecContext, entities []*domainmodel.Entity, moduleName string) (string, map[string]elkSourceRange) { sourceMap := make(map[string]elkSourceRange) var allSource strings.Builder lineCount := 0 for i, entity := range entities { qn := ast.QualifiedName{Module: moduleName, Name: entity.Name} - entityMdl, err := e.describeEntityToString(qn) + entityMdl, err := describeEntityToString(ctx, qn) if err != nil { continue } @@ -483,12 +487,12 @@ func (e *Executor) buildDomainModelMdlSource(entities []*domainmodel.Entity, mod } // emitDomainModelELK marshals and writes the domain model ELK data to output. -func (e *Executor) emitDomainModelELK(data domainModelELKData) error { +func emitDomainModelELK(ctx *ExecContext, data domainModelELKData) error { out, err := json.MarshalIndent(data, "", " ") if err != nil { return mdlerrors.NewBackend("marshal JSON", err) } - fmt.Fprint(e.output, string(out)) + fmt.Fprint(ctx.Output, string(out)) return nil } @@ -505,3 +509,15 @@ func classifyEntity(entity *domainmodel.Entity) string { } return "persistent" } + +// --- Executor method wrappers for callers not yet migrated --- + +// DomainModelELK is the exported Executor method, called from outside the package. +func (e *Executor) DomainModelELK(name string) error { + return domainModelELK(e.newExecContext(context.Background()), name) +} + +// EntityFocusELK is the exported Executor method. +func (e *Executor) EntityFocusELK(qualifiedName string) error { + return entityFocusELK(e.newExecContext(context.Background()), qualifiedName) +} diff --git a/mdl/executor/cmd_entities.go b/mdl/executor/cmd_entities.go index 477b22bb..c5c66c32 100644 --- a/mdl/executor/cmd_entities.go +++ b/mdl/executor/cmd_entities.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -18,14 +19,14 @@ import ( // buildEventHandlers converts a list of AST EventHandlerDef to domain model EventHandler. // Resolves the microflow qualified name to a microflow ID, but stores the qualified name // for BY_NAME serialization. -func (e *Executor) buildEventHandlers(defs []ast.EventHandlerDef) ([]*domainmodel.EventHandler, error) { +func buildEventHandlers(ctx *ExecContext, defs []ast.EventHandlerDef) ([]*domainmodel.EventHandler, error) { if len(defs) == 0 { return nil, nil } var handlers []*domainmodel.EventHandler for _, d := range defs { mfQN := d.Microflow.String() - mfID, err := e.resolveMicroflowByName(mfQN) + mfID, err := resolveMicroflowByName(ctx, mfQN) if err != nil { return nil, mdlerrors.NewNotFound("microflow", mfQN) } @@ -45,7 +46,8 @@ func (e *Executor) buildEventHandlers(defs []ast.EventHandlerDef) ([]*domainmode return handlers, nil } -func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { +func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -149,7 +151,7 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { Type: "CalculatedValue", } if a.CalculatedMicroflow != nil { - mfID, err := e.resolveMicroflowByName(a.CalculatedMicroflow.String()) + mfID, err := resolveMicroflowByName(ctx, a.CalculatedMicroflow.String()) if err != nil { return fmt.Errorf("attribute '%s': %w", a.Name, err) } @@ -241,7 +243,7 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { // Create entity // Build event handlers - eventHandlers, err := e.buildEventHandlers(s.EventHandlers) + eventHandlers, err := buildEventHandlers(ctx, s.EventHandlers) if err != nil { return err } @@ -275,7 +277,7 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { // Invalidate caches so updated entity is visible e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified entity: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Modified entity: %s\n", s.Name) } else { // Create new entity if err := e.writer.CreateEntity(dm.ID, entity); err != nil { @@ -284,7 +286,7 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { // Invalidate caches so new entity is visible e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Created entity: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created entity: %s\n", s.Name) } e.trackModifiedDomainModel(module.ID, module.Name) @@ -292,7 +294,8 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { } // execCreateViewEntity handles CREATE VIEW ENTITY statements. -func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { +func execCreateViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -445,7 +448,7 @@ func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { // Invalidate caches so updated entity is visible e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified view entity: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Modified view entity: %s\n", s.Name) } else { // Create new entity if err := e.writer.CreateEntity(dm.ID, entity); err != nil { @@ -454,14 +457,15 @@ func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { // Invalidate caches so new entity is visible e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Created view entity: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created view entity: %s\n", s.Name) } return nil } // execAlterEntity handles ALTER ENTITY statements. -func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { +func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -539,7 +543,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { Type: "CalculatedValue", } if a.CalculatedMicroflow != nil { - mfID, err := e.resolveMicroflowByName(a.CalculatedMicroflow.String()) + mfID, err := resolveMicroflowByName(ctx, a.CalculatedMicroflow.String()) if err != nil { return fmt.Errorf("attribute '%s': %w", a.Name, err) } @@ -596,7 +600,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { } e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added attribute '%s' to entity %s\n", a.Name, s.Name) + fmt.Fprintf(ctx.Output, "Added attribute '%s' to entity %s\n", a.Name, s.Name) case ast.AlterEntityRenameAttribute: found := false @@ -615,7 +619,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { } e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed attribute '%s' to '%s' on entity %s\n", s.AttributeName, s.NewName, s.Name) + fmt.Fprintf(ctx.Output, "Renamed attribute '%s' to '%s' on entity %s\n", s.AttributeName, s.NewName, s.Name) case ast.AlterEntityModifyAttribute: // CALCULATED attributes are only supported on persistent entities @@ -631,7 +635,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { Type: "CalculatedValue", } if s.CalculatedMicroflow != nil { - mfID, err := e.resolveMicroflowByName(s.CalculatedMicroflow.String()) + mfID, err := resolveMicroflowByName(ctx, s.CalculatedMicroflow.String()) if err != nil { return fmt.Errorf("attribute '%s': %w", s.AttributeName, err) } @@ -652,7 +656,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { } e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified attribute '%s' on entity %s\n", s.AttributeName, s.Name) + fmt.Fprintf(ctx.Output, "Modified attribute '%s' on entity %s\n", s.AttributeName, s.Name) case ast.AlterEntityDropAttribute: // System attribute pseudo-names: drop by clearing entity flags @@ -751,23 +755,23 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { } e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped attribute '%s' from entity %s\n", s.AttributeName, s.Name) + fmt.Fprintf(ctx.Output, "Dropped attribute '%s' from entity %s\n", s.AttributeName, s.Name) // Report what was cleaned up on the entity itself if n := origValidationCount - len(keepRules); n > 0 { - fmt.Fprintf(e.output, " Removed %d validation rule(s)\n", n) + fmt.Fprintf(ctx.Output, " Removed %d validation rule(s)\n", n) } if removedMemberAccess > 0 { - fmt.Fprintf(e.output, " Removed %d access rule member reference(s)\n", removedMemberAccess) + fmt.Fprintf(ctx.Output, " Removed %d access rule member reference(s)\n", removedMemberAccess) } if n := origIndexCount - len(keepIndexes); n > 0 { - fmt.Fprintf(e.output, " Removed %d index(es)\n", n) + fmt.Fprintf(ctx.Output, " Removed %d index(es)\n", n) } // Warn about references in other documents that are NOT auto-cleaned entityQName := s.Name.String() - fmt.Fprintf(e.output, " Warning: pages, microflows, and other documents may still reference '%s'. Update them manually.\n", s.AttributeName) - fmt.Fprintf(e.output, " Use SHOW REFERENCES TO %s to find usages (requires REFRESH CATALOG FULL).\n", entityQName) + fmt.Fprintf(ctx.Output, " Warning: pages, microflows, and other documents may still reference '%s'. Update them manually.\n", s.AttributeName) + fmt.Fprintf(ctx.Output, " Use SHOW REFERENCES TO %s to find usages (requires REFRESH CATALOG FULL).\n", entityQName) case ast.AlterEntitySetDocumentation: entity.Documentation = s.Documentation @@ -775,7 +779,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("set documentation", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Set documentation on entity %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Set documentation on entity %s\n", s.Name) case ast.AlterEntitySetComment: // Comments are stored as documentation in the Mendix model @@ -784,7 +788,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("set comment", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Set comment on entity %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Set comment on entity %s\n", s.Name) case ast.AlterEntitySetPosition: if s.Position == nil { @@ -795,7 +799,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("set position", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Set position of entity %s to (%d, %d)\n", s.Name, s.Position.X, s.Position.Y) + fmt.Fprintf(ctx.Output, "Set position of entity %s to (%d, %d)\n", s.Name, s.Position.X, s.Position.Y) case ast.AlterEntityAddIndex: if s.Index == nil { @@ -829,7 +833,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("add index", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added index to entity %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Added index to entity %s\n", s.Name) case ast.AlterEntityDropIndex: // Find and remove the index by position (Mendix indexes don't have user-visible names) @@ -853,13 +857,13 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("drop index", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped index '%s' from entity %s\n", s.IndexName, s.Name) + fmt.Fprintf(ctx.Output, "Dropped index '%s' from entity %s\n", s.IndexName, s.Name) case ast.AlterEntityAddEventHandler: if s.EventHandler == nil { return mdlerrors.NewValidation("missing event handler definition") } - ehs, err := e.buildEventHandlers([]ast.EventHandlerDef{*s.EventHandler}) + ehs, err := buildEventHandlers(ctx, []ast.EventHandlerDef{*s.EventHandler}) if err != nil { return err } @@ -876,7 +880,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("add event handler", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added event handler %s %s on %s\n", + fmt.Fprintf(ctx.Output, "Added event handler %s %s on %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) case ast.AlterEntityDropEventHandler: @@ -902,7 +906,7 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { return mdlerrors.NewBackend("drop event handler", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped event handler %s %s from %s\n", + fmt.Fprintf(ctx.Output, "Dropped event handler %s %s from %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) default: @@ -914,7 +918,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { } // execDropEntity handles DROP ENTITY statements. -func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { +func execDropEntity(ctx *ExecContext, s *ast.DropEntityStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -933,7 +938,7 @@ func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { for _, entity := range dm.Entities { if entity.Name == s.Name.Name { // Warn about references before deleting (best-effort) - e.warnEntityReferences(s.Name.String()) + warnEntityReferences(ctx, s.Name.String()) // If this is a view entity, also delete the associated ViewEntitySourceDocument if entity.Source == "DomainModels$OqlViewEntitySource" { @@ -945,7 +950,7 @@ func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { return mdlerrors.NewBackend("delete entity", err) } e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped entity: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped entity: %s\n", s.Name) return nil } } @@ -955,7 +960,8 @@ func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { // warnEntityReferences prints a warning if the entity is referenced by other elements. // Uses the catalog if available; silently skips if catalog is not built. -func (e *Executor) warnEntityReferences(entityName string) { +func warnEntityReferences(ctx *ExecContext, entityName string) { + e := ctx.executor if e.catalog == nil || !e.catalog.IsBuilt() { return } @@ -969,11 +975,29 @@ func (e *Executor) warnEntityReferences(entityName string) { return } - fmt.Fprintf(e.output, "WARNING: %s is referenced by %d element(s):\n", entityName, result.Count) + fmt.Fprintf(ctx.Output, "WARNING: %s is referenced by %d element(s):\n", entityName, result.Count) for _, row := range result.Rows { sourceType, _ := row[0].(string) sourceName, _ := row[1].(string) refKind, _ := row[2].(string) - fmt.Fprintf(e.output, " - %s %s (%s)\n", sourceType, sourceName, refKind) + fmt.Fprintf(ctx.Output, " - %s %s (%s)\n", sourceType, sourceName, refKind) } } + +// --- Executor method wrappers for callers not yet migrated --- + +func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { + return execCreateEntity(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { + return execCreateViewEntity(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { + return execAlterEntity(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { + return execDropEntity(e.newExecContext(context.Background()), s) +} diff --git a/mdl/executor/cmd_entities_access.go b/mdl/executor/cmd_entities_access.go index 9aa5df28..a6967325 100644 --- a/mdl/executor/cmd_entities_access.go +++ b/mdl/executor/cmd_entities_access.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -11,7 +12,7 @@ import ( ) // outputEntityAccessGrants outputs GRANT statements for entity access rules. -func (e *Executor) outputEntityAccessGrants(entity *domainmodel.Entity, moduleName, entityName string) { +func outputEntityAccessGrants(ctx *ExecContext, entity *domainmodel.Entity, moduleName, entityName string) { if len(entity.AccessRules) == 0 { return } @@ -37,7 +38,7 @@ func (e *Executor) outputEntityAccessGrants(entity *domainmodel.Entity, moduleNa continue } - rightsStr := e.formatAccessRuleRights(rule, attrNames) + rightsStr := formatAccessRuleRights(ctx, rule, attrNames) if rightsStr == "" { continue } @@ -50,13 +51,13 @@ func (e *Executor) outputEntityAccessGrants(entity *domainmodel.Entity, moduleNa } grantLine += ";" - fmt.Fprintln(e.output, grantLine) + fmt.Fprintln(ctx.Output, grantLine) } } // resolveEntityMemberAccess determines per-member READ/WRITE access. // Returns nil slices for "all members" (*), or specific member name lists. -func (e *Executor) resolveEntityMemberAccess(rule *domainmodel.AccessRule, attrNames map[string]string) (readMembers []string, writeMembers []string) { +func resolveEntityMemberAccess(_ *ExecContext, rule *domainmodel.AccessRule, attrNames map[string]string) (readMembers []string, writeMembers []string) { if len(rule.MemberAccesses) == 0 { // No per-member overrides: use default return nil, nil @@ -117,7 +118,7 @@ func (e *Executor) resolveEntityMemberAccess(rule *domainmodel.AccessRule, attrN // formatAccessRuleRights formats the rights portion of an access rule as a string. // Returns a string like "CREATE, DELETE, READ (Name, Price), WRITE (Price)" or empty if no rights. -func (e *Executor) formatAccessRuleRights(rule *domainmodel.AccessRule, attrNames map[string]string) string { +func formatAccessRuleRights(ctx *ExecContext, rule *domainmodel.AccessRule, attrNames map[string]string) string { var rights []string if rule.AllowCreate { rights = append(rights, "CREATE") @@ -141,7 +142,7 @@ func (e *Executor) formatAccessRuleRights(rule *domainmodel.AccessRule, attrName } } - readMembers, writeMembers := e.resolveEntityMemberAccess(rule, attrNames) + readMembers, writeMembers := resolveEntityMemberAccess(ctx, rule, attrNames) if hasRead { if readMembers == nil { @@ -163,7 +164,8 @@ func (e *Executor) formatAccessRuleRights(rule *domainmodel.AccessRule, attrName // formatAccessRuleResult re-reads the entity and formats the resulting access state // for the given roles. Returns a string like " Result: CREATE, READ (Name, Price)\n". -func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNames []string) string { +func formatAccessRuleResult(ctx *ExecContext, moduleName, entityName string, roleNames []string) string { + e := ctx.executor e.invalidateDomainModelsCache() module, err := e.findModule(moduleName) @@ -204,7 +206,7 @@ func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNam continue } // Found a matching rule - rightsStr := e.formatAccessRuleRights(rule, attrNames) + rightsStr := formatAccessRuleRights(ctx, rule, attrNames) if rightsStr == "" { return " Result: (no access)\n" } @@ -213,3 +215,21 @@ func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNam return " Result: (no access)\n" } + +// --- Executor method wrappers for callers not yet migrated --- + +func (e *Executor) outputEntityAccessGrants(entity *domainmodel.Entity, moduleName, entityName string) { + outputEntityAccessGrants(e.newExecContext(context.Background()), entity, moduleName, entityName) +} + +func (e *Executor) formatAccessRuleRights(rule *domainmodel.AccessRule, attrNames map[string]string) string { + return formatAccessRuleRights(e.newExecContext(context.Background()), rule, attrNames) +} + +func (e *Executor) resolveEntityMemberAccess(rule *domainmodel.AccessRule, attrNames map[string]string) ([]string, []string) { + return resolveEntityMemberAccess(e.newExecContext(context.Background()), rule, attrNames) +} + +func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNames []string) string { + return formatAccessRuleResult(e.newExecContext(context.Background()), moduleName, entityName, roleNames) +} diff --git a/mdl/executor/cmd_entities_describe.go b/mdl/executor/cmd_entities_describe.go index 0999df46..487933f7 100644 --- a/mdl/executor/cmd_entities_describe.go +++ b/mdl/executor/cmd_entities_describe.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,8 @@ import ( ) // showEntities handles SHOW ENTITIES command. -func (e *Executor) showEntities(moduleName string) error { +func showEntities(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Build module ID -> name map (single query) modules, err := e.reader.ListModules() if err != nil { @@ -157,7 +159,8 @@ func (e *Executor) showEntities(moduleName string) error { } // showEntity handles SHOW ENTITY command. -func (e *Executor) showEntity(name *ast.QualifiedName) error { +func showEntity(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("entity name required") } @@ -174,12 +177,12 @@ func (e *Executor) showEntity(name *ast.QualifiedName) error { for _, entity := range dm.Entities { if entity.Name == name.Name { - fmt.Fprintf(e.output, "**Entity: %s.%s**\n\n", module.Name, entity.Name) - fmt.Fprintf(e.output, "- Persistable: %v\n", entity.Persistable) + fmt.Fprintf(ctx.Output, "**Entity: %s.%s**\n\n", module.Name, entity.Name) + fmt.Fprintf(ctx.Output, "- Persistable: %v\n", entity.Persistable) if entity.GeneralizationRef != "" { - fmt.Fprintf(e.output, "- Extends: %s\n", entity.GeneralizationRef) + fmt.Fprintf(ctx.Output, "- Extends: %s\n", entity.GeneralizationRef) } - fmt.Fprintf(e.output, "- Location: (%d, %d)\n\n", entity.Location.X, entity.Location.Y) + fmt.Fprintf(ctx.Output, "- Location: (%d, %d)\n\n", entity.Location.X, entity.Location.Y) if len(entity.Attributes) > 0 { // Calculate column widths @@ -199,12 +202,12 @@ func (e *Executor) showEntity(name *ast.QualifiedName) error { } } - fmt.Fprintf(e.output, "| %-*s | %-*s |\n", nameWidth, "Attribute", typeWidth, "Type") - fmt.Fprintf(e.output, "|-%s-|-%s-|\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth)) + fmt.Fprintf(ctx.Output, "| %-*s | %-*s |\n", nameWidth, "Attribute", typeWidth, "Type") + fmt.Fprintf(ctx.Output, "|-%s-|-%s-|\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth)) for _, r := range rows { - fmt.Fprintf(e.output, "| %-*s | %-*s |\n", nameWidth, r.name, typeWidth, r.typeName) + fmt.Fprintf(ctx.Output, "| %-*s | %-*s |\n", nameWidth, r.name, typeWidth, r.typeName) } - fmt.Fprintf(e.output, "\n(%d attributes)\n", len(entity.Attributes)) + fmt.Fprintf(ctx.Output, "\n(%d attributes)\n", len(entity.Attributes)) } return nil } @@ -214,7 +217,8 @@ func (e *Executor) showEntity(name *ast.QualifiedName) error { } // describeEntity handles DESCRIBE ENTITY command. -func (e *Executor) describeEntity(name ast.QualifiedName) error { +func describeEntity(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor module, err := e.findModule(name.Module) if err != nil { return err @@ -229,11 +233,11 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { if entity.Name == name.Name { // Output JavaDoc documentation if present if entity.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", entity.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", entity.Documentation) } // Output position annotation - fmt.Fprintf(e.output, "@Position(%d, %d)\n", entity.Location.X, entity.Location.Y) + fmt.Fprintf(ctx.Output, "@Position(%d, %d)\n", entity.Location.X, entity.Location.Y) // Determine entity type based on Source field and Persistable flag entityType := "PERSISTENT" @@ -246,9 +250,9 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { } if entity.GeneralizationRef != "" { - fmt.Fprintf(e.output, "CREATE OR MODIFY %s ENTITY %s.%s EXTENDS %s (\n", entityType, module.Name, entity.Name, entity.GeneralizationRef) + fmt.Fprintf(ctx.Output, "CREATE OR MODIFY %s ENTITY %s.%s EXTENDS %s (\n", entityType, module.Name, entity.Name, entity.GeneralizationRef) } else { - fmt.Fprintf(e.output, "CREATE OR MODIFY %s ENTITY %s.%s (\n", entityType, module.Name, entity.Name) + fmt.Fprintf(ctx.Output, "CREATE OR MODIFY %s ENTITY %s.%s (\n", entityType, module.Name, entity.Name) } // Build validation rules map by attribute ID and name @@ -314,7 +318,7 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { if attr.Value.MicroflowName != "" { constraints.WriteString(" BY " + attr.Value.MicroflowName) } else if attr.Value.MicroflowID != "" { - if mfName := e.lookupMicroflowName(attr.Value.MicroflowID); mfName != "" { + if mfName := lookupMicroflowName(ctx, attr.Value.MicroflowID); mfName != "" { constraints.WriteString(" BY " + mfName) } } @@ -357,19 +361,19 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { if i == len(attrLines)-1 { comma = "" } - fmt.Fprintf(e.output, "%s%s\n", al.text, comma) + fmt.Fprintf(ctx.Output, "%s%s\n", al.text, comma) } - fmt.Fprint(e.output, ")") + fmt.Fprint(ctx.Output, ")") // For VIEW entities, output the OQL query if entityType == "VIEW" && entity.OqlQuery != "" { - fmt.Fprint(e.output, " AS (\n") + fmt.Fprint(ctx.Output, " AS (\n") // Indent OQL query lines oqlLines := strings.SplitSeq(entity.OqlQuery, "\n") for line := range oqlLines { - fmt.Fprintf(e.output, " %s\n", line) + fmt.Fprintf(ctx.Output, " %s\n", line) } - fmt.Fprint(e.output, ")") + fmt.Fprint(ctx.Output, ")") } // Build attribute name map @@ -389,7 +393,7 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { cols = append(cols, colName) } if len(cols) > 0 { - fmt.Fprintf(e.output, "\nINDEX (%s)", strings.Join(cols, ", ")) + fmt.Fprintf(ctx.Output, "\nINDEX (%s)", strings.Join(cols, ", ")) } } @@ -397,7 +401,7 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { for _, eh := range entity.EventHandlers { mfName := eh.MicroflowName if mfName == "" && eh.MicroflowID != "" { - mfName = e.lookupMicroflowName(eh.MicroflowID) + mfName = lookupMicroflowName(ctx, eh.MicroflowID) } if mfName == "" { continue @@ -418,16 +422,16 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { if eh.RaiseErrorOnFalse && strings.EqualFold(string(eh.Moment), "Before") { options = " RAISE ERROR" } - fmt.Fprintf(e.output, "\nON %s %s CALL %s%s%s", + fmt.Fprintf(ctx.Output, "\nON %s %s CALL %s%s%s", strings.ToUpper(string(eh.Moment)), eventName, mfName, paramStr, options) } - fmt.Fprintln(e.output, ";") + fmt.Fprintln(ctx.Output, ";") // Output access rule GRANT statements - e.outputEntityAccessGrants(entity, name.Module, name.Name) + outputEntityAccessGrants(ctx, entity, name.Module, name.Name) - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } } @@ -436,12 +440,12 @@ func (e *Executor) describeEntity(name ast.QualifiedName) error { } // describeEntityToString generates MDL source for an entity and returns it as a string. -func (e *Executor) describeEntityToString(name ast.QualifiedName) (string, error) { +func describeEntityToString(ctx *ExecContext, name ast.QualifiedName) (string, error) { var buf strings.Builder - origOutput := e.output - e.output = &buf - err := e.describeEntity(name) - e.output = origOutput + origOutput := ctx.Output + ctx.Output = &buf + err := describeEntity(ctx, name) + ctx.Output = origOutput if err != nil { return "", err } @@ -461,7 +465,8 @@ func extractAttrNameFromQualified(qualifiedName string) string { // resolveMicroflowByName resolves a qualified microflow name to its ID. // It checks both microflows created during this session and existing microflows in the project. -func (e *Executor) resolveMicroflowByName(qualifiedName string) (model.ID, error) { +func resolveMicroflowByName(ctx *ExecContext, qualifiedName string) (model.ID, error) { + e := ctx.executor parts := strings.Split(qualifiedName, ".") if len(parts) < 2 { return "", mdlerrors.NewValidationf("invalid microflow name: %s (expected Module.Name)", qualifiedName) @@ -499,7 +504,8 @@ func (e *Executor) resolveMicroflowByName(qualifiedName string) (model.ID, error } // lookupMicroflowName reverse-looks up a microflow ID to its qualified name. -func (e *Executor) lookupMicroflowName(mfID model.ID) string { +func lookupMicroflowName(ctx *ExecContext, mfID model.ID) string { + e := ctx.executor allMicroflows, err := e.reader.ListMicroflows() if err != nil { return "" @@ -522,3 +528,29 @@ func (e *Executor) lookupMicroflowName(mfID model.ID) string { } return "" } + +// --- Executor method wrappers for callers not yet migrated --- + +func (e *Executor) showEntities(moduleName string) error { + return showEntities(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) showEntity(name *ast.QualifiedName) error { + return showEntity(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeEntity(name ast.QualifiedName) error { + return describeEntity(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeEntityToString(name ast.QualifiedName) (string, error) { + return describeEntityToString(e.newExecContext(context.Background()), name) +} + +func (e *Executor) resolveMicroflowByName(qualifiedName string) (model.ID, error) { + return resolveMicroflowByName(e.newExecContext(context.Background()), qualifiedName) +} + +func (e *Executor) lookupMicroflowName(mfID model.ID) string { + return lookupMicroflowName(e.newExecContext(context.Background()), mfID) +} diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 655b7bfd..a2f66ffa 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -59,28 +59,28 @@ func registerDatabaseConnectionHandlers(r *Registry) { func registerEntityHandlers(r *Registry) { r.Register(&ast.CreateEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateEntity(stmt.(*ast.CreateEntityStmt)) + return execCreateEntity(ctx, stmt.(*ast.CreateEntityStmt)) }) r.Register(&ast.CreateViewEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateViewEntity(stmt.(*ast.CreateViewEntityStmt)) + return execCreateViewEntity(ctx, stmt.(*ast.CreateViewEntityStmt)) }) r.Register(&ast.AlterEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterEntity(stmt.(*ast.AlterEntityStmt)) + return execAlterEntity(ctx, stmt.(*ast.AlterEntityStmt)) }) r.Register(&ast.DropEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropEntity(stmt.(*ast.DropEntityStmt)) + return execDropEntity(ctx, stmt.(*ast.DropEntityStmt)) }) } func registerAssociationHandlers(r *Registry) { r.Register(&ast.CreateAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateAssociation(stmt.(*ast.CreateAssociationStmt)) + return execCreateAssociation(ctx, stmt.(*ast.CreateAssociationStmt)) }) r.Register(&ast.AlterAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterAssociation(stmt.(*ast.AlterAssociationStmt)) + return execAlterAssociation(ctx, stmt.(*ast.AlterAssociationStmt)) }) r.Register(&ast.DropAssociationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropAssociation(stmt.(*ast.DropAssociationStmt)) + return execDropAssociation(ctx, stmt.(*ast.DropAssociationStmt)) }) } From 13be46ae108248225682e1d1caa14e3e9dcb21da Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:10:46 +0200 Subject: [PATCH 06/16] refactor: migrate microflow handlers to free functions --- mdl/executor/cmd_microflow_elk.go | 25 +++-- mdl/executor/cmd_microflows_create.go | 12 ++- mdl/executor/cmd_microflows_drop.go | 5 +- mdl/executor/cmd_microflows_format_action.go | 58 ++++++---- mdl/executor/cmd_microflows_show.go | 102 ++++++++++++------ mdl/executor/cmd_microflows_show_helpers.go | 107 +++++++++++++------ mdl/executor/register_stubs.go | 4 +- 7 files changed, 210 insertions(+), 103 deletions(-) diff --git a/mdl/executor/cmd_microflow_elk.go b/mdl/executor/cmd_microflow_elk.go index d07a4202..7b1fb21f 100644 --- a/mdl/executor/cmd_microflow_elk.go +++ b/mdl/executor/cmd_microflow_elk.go @@ -3,6 +3,7 @@ package executor import ( + "context" "encoding/json" "fmt" "math" @@ -59,8 +60,9 @@ type microflowELKEdge struct { IsErrorHandler bool `json:"isErrorHandler,omitempty"` } -// MicroflowELK generates a JSON graph of a microflow for rendering with ELK.js. -func (e *Executor) MicroflowELK(name string) error { +// microflowELK generates a JSON graph of a microflow for rendering with ELK.js. +func microflowELK(ctx *ExecContext, name string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -108,12 +110,12 @@ func (e *Executor) MicroflowELK(name string) error { } // Generate MDL source with source map - mdlSource, sourceMap, _ := e.describeMicroflowToString(qn) + mdlSource, sourceMap, _ := describeMicroflowToString(ctx, qn) - return e.buildMicroflowELK(targetMf, name, entityNames, mdlSource, sourceMap) + return buildMicroflowELK(ctx, targetMf, name, entityNames, mdlSource, sourceMap) } -func (e *Executor) buildMicroflowELK(mf *microflows.Microflow, qualifiedName string, entityNames map[model.ID]string, mdlSource string, sourceMap map[string]elkSourceRange) error { +func buildMicroflowELK(ctx *ExecContext, mf *microflows.Microflow, qualifiedName string, entityNames map[model.ID]string, mdlSource string, sourceMap map[string]elkSourceRange) error { returnType := "" if mf.ReturnType != nil { returnType = mf.ReturnType.GetTypeName() @@ -149,7 +151,7 @@ func (e *Executor) buildMicroflowELK(mf *microflows.Microflow, qualifiedName str data.Edges = []microflowELKEdge{ {ID: "edge-0", SourceID: "node-start", TargetID: "node-end"}, } - return e.emitMicroflowELK(data) + return emitMicroflowELK(ctx, data) } // Build nodes — loops become compound nodes with children @@ -164,7 +166,7 @@ func (e *Executor) buildMicroflowELK(mf *microflows.Microflow, qualifiedName str data.Edges = append(data.Edges, edge) } - return e.emitMicroflowELK(data) + return emitMicroflowELK(ctx, data) } func buildMicroflowELKNode(obj microflows.MicroflowObject, entityNames map[model.ID]string) microflowELKNode { @@ -403,11 +405,16 @@ func collectAllObjectsAndFlows(oc *microflows.MicroflowObjectCollection) ([]micr } // emitMicroflowELK marshals and writes the microflow ELK data to output. -func (e *Executor) emitMicroflowELK(data microflowELKData) error { +func emitMicroflowELK(ctx *ExecContext, data microflowELKData) error { out, err := json.MarshalIndent(data, "", " ") if err != nil { return mdlerrors.NewBackend("marshal JSON", err) } - fmt.Fprint(e.output, string(out)) + fmt.Fprint(ctx.Output, string(out)) return nil } + +// MicroflowELK is an Executor method wrapper for callers in unmigrated code. +func (e *Executor) MicroflowELK(name string) error { + return microflowELK(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_microflows_create.go b/mdl/executor/cmd_microflows_create.go index 218b8b5a..1e54c595 100644 --- a/mdl/executor/cmd_microflows_create.go +++ b/mdl/executor/cmd_microflows_create.go @@ -24,7 +24,8 @@ func isBuiltinModuleEntity(moduleName string) bool { // execCreateMicroflow handles CREATE MICROFLOW statements. // loadRestServices returns all consumed REST services, or nil if no reader. -func (e *Executor) loadRestServices() ([]*model.ConsumedRestService, error) { +func loadRestServices(ctx *ExecContext) ([]*model.ConsumedRestService, error) { + e := ctx.executor if e.reader == nil { return nil, nil } @@ -32,7 +33,8 @@ func (e *Executor) loadRestServices() ([]*model.ConsumedRestService, error) { return svcs, err } -func (e *Executor) execCreateMicroflow(s *ast.CreateMicroflowStmt) error { +func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -202,7 +204,7 @@ func (e *Executor) execCreateMicroflow(s *ast.CreateMicroflowStmt) error { // Get hierarchy for resolving page/microflow references hierarchy, _ := e.getHierarchy() - restServices, _ := e.loadRestServices() + restServices, _ := loadRestServices(ctx) builder := &flowBuilder{ posX: 200, @@ -235,12 +237,12 @@ func (e *Executor) execCreateMicroflow(s *ast.CreateMicroflowStmt) error { if err := e.writer.UpdateMicroflow(mf); err != nil { return mdlerrors.NewBackend("update microflow", err) } - fmt.Fprintf(e.output, "Replaced microflow: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Replaced microflow: %s.%s\n", s.Name.Module, s.Name.Name) } else { if err := e.writer.CreateMicroflow(mf); err != nil { return mdlerrors.NewBackend("create microflow", err) } - fmt.Fprintf(e.output, "Created microflow: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created microflow: %s.%s\n", s.Name.Module, s.Name.Name) } // Track the created microflow so it can be resolved by subsequent page creations diff --git a/mdl/executor/cmd_microflows_drop.go b/mdl/executor/cmd_microflows_drop.go index 0f9a59c6..5bc1d1b9 100644 --- a/mdl/executor/cmd_microflows_drop.go +++ b/mdl/executor/cmd_microflows_drop.go @@ -11,7 +11,8 @@ import ( ) // execDropMicroflow handles DROP MICROFLOW statements. -func (e *Executor) execDropMicroflow(s *ast.DropMicroflowStmt) error { +func execDropMicroflow(ctx *ExecContext, s *ast.DropMicroflowStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -41,7 +42,7 @@ func (e *Executor) execDropMicroflow(s *ast.DropMicroflowStmt) error { delete(e.cache.createdMicroflows, qualifiedName) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped microflow: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped microflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } } diff --git a/mdl/executor/cmd_microflows_format_action.go b/mdl/executor/cmd_microflows_format_action.go index ce277326..d0a4f247 100644 --- a/mdl/executor/cmd_microflows_format_action.go +++ b/mdl/executor/cmd_microflows_format_action.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -12,7 +13,8 @@ import ( ) // formatActivity formats a single microflow activity as an MDL statement. -func (e *Executor) formatActivity( +func formatActivity( + ctx *ExecContext, obj microflows.MicroflowObject, entityNames map[model.ID]string, microflowNames map[model.ID]string, @@ -34,7 +36,7 @@ func (e *Executor) formatActivity( return "" // Skip end events without return value case *microflows.ActionActivity: - return e.formatAction(activity.Action, entityNames, microflowNames) + return formatAction(ctx, activity.Action, entityNames, microflowNames) case *microflows.ExclusiveSplit: condition := "true" @@ -84,11 +86,13 @@ func (e *Executor) formatActivity( } // formatAction formats a microflow action as an MDL statement. -func (e *Executor) formatAction( +func formatAction( + ctx *ExecContext, action microflows.MicroflowAction, entityNames map[model.ID]string, microflowNames map[model.ID]string, ) string { + e := ctx.executor if action == nil { return "-- Empty action" } @@ -97,7 +101,7 @@ func (e *Executor) formatAction( case *microflows.CreateVariableAction: varType := "Object" if a.DataType != nil { - varType = e.formatMicroflowDataType(a.DataType, entityNames) + varType = formatMicroflowDataType(ctx, a.DataType, entityNames) } initialValue := strings.TrimSuffix(a.InitialValue, "\n") if initialValue == "" { @@ -255,7 +259,7 @@ func (e *Executor) formatAction( if outputVar == "" { outputVar = "Result" } - return e.formatListOperation(a.Operation, outputVar) + return formatListOperation(ctx, a.Operation, outputVar) case *microflows.AggregateListAction: outputVar := a.OutputVariable @@ -592,19 +596,19 @@ func (e *Executor) formatAction( return fmt.Sprintf("VALIDATION FEEDBACK %s MESSAGE %s;", attrPath, msgText) case *microflows.RestCallAction: - return e.formatRestCallAction(a) + return formatRestCallAction(ctx, a) case *microflows.RestOperationCallAction: - return e.formatRestOperationCallAction(a) + return formatRestOperationCallAction(ctx, a) case *microflows.ExecuteDatabaseQueryAction: - return e.formatExecuteDatabaseQueryAction(a) + return formatExecuteDatabaseQueryAction(ctx, a) case *microflows.ImportXmlAction: - return e.formatImportXmlAction(a, entityNames) + return formatImportXmlAction(ctx, a, entityNames) case *microflows.ExportXmlAction: - return e.formatExportXmlAction(a) + return formatExportXmlAction(ctx, a) case *microflows.TransformJsonAction: return formatTransformJsonAction(a) @@ -635,7 +639,7 @@ func (e *Executor) formatAction( return fmt.Sprintf("GET WORKFLOW ACTIVITY RECORDS $%s;", a.WorkflowVariable) case *microflows.WorkflowOperationAction: - return e.formatWorkflowOperationAction(a) + return formatWorkflowOperationAction(ctx, a) case *microflows.SetTaskOutcomeAction: return fmt.Sprintf("SET TASK OUTCOME $%s '%s';", a.WorkflowTaskVariable, a.OutcomeValue) @@ -679,7 +683,7 @@ func (e *Executor) formatAction( } // formatWorkflowOperationAction formats a workflow operation action as MDL. -func (e *Executor) formatWorkflowOperationAction(a *microflows.WorkflowOperationAction) string { +func formatWorkflowOperationAction(ctx *ExecContext, a *microflows.WorkflowOperationAction) string { if a.Operation == nil { return "WORKFLOW OPERATION ...;" } @@ -705,7 +709,7 @@ func (e *Executor) formatWorkflowOperationAction(a *microflows.WorkflowOperation } // formatListOperation formats a list operation as MDL. -func (e *Executor) formatListOperation(op microflows.ListOperation, outputVar string) string { +func formatListOperation(ctx *ExecContext, op microflows.ListOperation, outputVar string) string { if op == nil { return fmt.Sprintf("$%s = LIST OPERATION ...;", outputVar) } @@ -759,7 +763,7 @@ func (e *Executor) formatListOperation(op microflows.ListOperation, outputVar st } // formatRestCallAction formats a REST call action as MDL. -func (e *Executor) formatRestCallAction(a *microflows.RestCallAction) string { +func formatRestCallAction(ctx *ExecContext, a *microflows.RestCallAction) string { var sb strings.Builder // Output variable assignment (may be on RestCallAction or ResultHandling) @@ -905,7 +909,7 @@ func (e *Executor) formatRestCallAction(a *microflows.RestCallAction) string { } // formatRestOperationCallAction formats a RestOperationCallAction as MDL. -func (e *Executor) formatRestOperationCallAction(a *microflows.RestOperationCallAction) string { +func formatRestOperationCallAction(ctx *ExecContext, a *microflows.RestOperationCallAction) string { var sb strings.Builder if a.OutputVariable != nil && a.OutputVariable.VariableName != "" { @@ -961,7 +965,7 @@ func (e *Executor) formatRestOperationCallAction(a *microflows.RestOperationCall } // formatExecuteDatabaseQueryAction formats a DatabaseConnector ExecuteDatabaseQueryAction as MDL. -func (e *Executor) formatExecuteDatabaseQueryAction(a *microflows.ExecuteDatabaseQueryAction) string { +func formatExecuteDatabaseQueryAction(ctx *ExecContext, a *microflows.ExecuteDatabaseQueryAction) string { var sb strings.Builder if a.OutputVariableName != "" { @@ -1022,7 +1026,7 @@ func isQualifiedEnumLiteral(s string) bool { // formatImportXmlAction formats an import mapping action as MDL. // Syntax: [$Var =] IMPORT FROM MAPPING Module.IMM($SourceVar); -func (e *Executor) formatImportXmlAction(a *microflows.ImportXmlAction, entityNames map[model.ID]string) string { +func formatImportXmlAction(ctx *ExecContext, a *microflows.ImportXmlAction, entityNames map[model.ID]string) string { var sb strings.Builder // Resolve mapping qualified name @@ -1051,7 +1055,7 @@ func (e *Executor) formatImportXmlAction(a *microflows.ImportXmlAction, entityNa // formatExportXmlAction formats an export mapping action as MDL. // Syntax: $Var = EXPORT TO MAPPING Module.EMM($SourceVar); -func (e *Executor) formatExportXmlAction(a *microflows.ExportXmlAction) string { +func formatExportXmlAction(ctx *ExecContext, a *microflows.ExportXmlAction) string { var sb strings.Builder // Output variable @@ -1097,3 +1101,21 @@ func formatTransformJsonAction(a *microflows.TransformJsonAction) string { sb.WriteString(";") return sb.String() } + +// --- Executor method wrappers for callers in unmigrated code and tests --- + +func (e *Executor) formatActivity(obj microflows.MicroflowObject, entityNames map[model.ID]string, microflowNames map[model.ID]string) string { + return formatActivity(e.newExecContext(context.Background()), obj, entityNames, microflowNames) +} + +func (e *Executor) formatAction(action microflows.MicroflowAction, entityNames map[model.ID]string, microflowNames map[model.ID]string) string { + return formatAction(e.newExecContext(context.Background()), action, entityNames, microflowNames) +} + +func (e *Executor) formatListOperation(op microflows.ListOperation, outputVar string) string { + return formatListOperation(e.newExecContext(context.Background()), op, outputVar) +} + +func (e *Executor) formatRestCallAction(a *microflows.RestCallAction) string { + return formatRestCallAction(e.newExecContext(context.Background()), a) +} diff --git a/mdl/executor/cmd_microflows_show.go b/mdl/executor/cmd_microflows_show.go index 6b286acc..5bc4c014 100644 --- a/mdl/executor/cmd_microflows_show.go +++ b/mdl/executor/cmd_microflows_show.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,8 @@ import ( ) // showMicroflows handles SHOW MICROFLOWS command. -func (e *Executor) showMicroflows(moduleName string) error { +func showMicroflows(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -79,7 +81,8 @@ func (e *Executor) showMicroflows(moduleName string) error { } // showNanoflows handles SHOW NANOFLOWS command. -func (e *Executor) showNanoflows(moduleName string) error { +func showNanoflows(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -177,7 +180,8 @@ func calculateNanoflowComplexity(nf *microflows.Nanoflow) int { } // describeMicroflow handles DESCRIBE MICROFLOW command - outputs MDL source code. -func (e *Executor) describeMicroflow(name ast.QualifiedName) error { +func describeMicroflow(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -239,7 +243,7 @@ func (e *Executor) describeMicroflow(name ast.QualifiedName) error { for i, param := range targetMf.Parameters { paramType := "Object" if param.Type != nil { - paramType = e.formatMicroflowDataType(param.Type, entityNames) + paramType = formatMicroflowDataType(ctx, param.Type, entityNames) } comma := "," if i == len(targetMf.Parameters)-1 { @@ -254,7 +258,7 @@ func (e *Executor) describeMicroflow(name ast.QualifiedName) error { // Return type if targetMf.ReturnType != nil { - returnType := e.formatMicroflowDataType(targetMf.ReturnType, entityNames) + returnType := formatMicroflowDataType(ctx, targetMf.ReturnType, entityNames) if returnType != "Void" && returnType != "" { returnLine := fmt.Sprintf("RETURNS %s", returnType) // Add variable name if specified (AS $VarName) @@ -275,7 +279,7 @@ func (e *Executor) describeMicroflow(name ast.QualifiedName) error { // Generate activities if targetMf.ObjectCollection != nil && len(targetMf.ObjectCollection.Objects) > 0 { - activityLines := e.formatMicroflowActivities(targetMf, entityNames, microflowNames) + activityLines := formatMicroflowActivities(ctx, targetMf, entityNames, microflowNames) for _, line := range activityLines { lines = append(lines, " "+line) } @@ -299,13 +303,14 @@ func (e *Executor) describeMicroflow(name ast.QualifiedName) error { lines = append(lines, "/") // Output - fmt.Fprintln(e.output, strings.Join(lines, "\n")) + fmt.Fprintln(ctx.Output, strings.Join(lines, "\n")) return nil } // describeNanoflow generates re-executable CREATE OR MODIFY NANOFLOW MDL output // with activities and control flows listed as comments. -func (e *Executor) describeNanoflow(name ast.QualifiedName) error { +func describeNanoflow(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -370,7 +375,7 @@ func (e *Executor) describeNanoflow(name ast.QualifiedName) error { for i, param := range targetNf.Parameters { paramType := "Object" if param.Type != nil { - paramType = e.formatMicroflowDataType(param.Type, entityNames) + paramType = formatMicroflowDataType(ctx, param.Type, entityNames) } comma := "," if i == len(targetNf.Parameters)-1 { @@ -385,7 +390,7 @@ func (e *Executor) describeNanoflow(name ast.QualifiedName) error { // Return type if targetNf.ReturnType != nil { - returnType := e.formatMicroflowDataType(targetNf.ReturnType, entityNames) + returnType := formatMicroflowDataType(ctx, targetNf.ReturnType, entityNames) if returnType != "Void" && returnType != "" { lines = append(lines, fmt.Sprintf("RETURNS %s", returnType)) } @@ -404,7 +409,7 @@ func (e *Executor) describeNanoflow(name ast.QualifiedName) error { wrapperMf := µflows.Microflow{ ObjectCollection: targetNf.ObjectCollection, } - activityLines := e.formatMicroflowActivities(wrapperMf, entityNames, microflowNames) + activityLines := formatMicroflowActivities(ctx, wrapperMf, entityNames, microflowNames) for _, line := range activityLines { lines = append(lines, " "+line) } @@ -415,13 +420,14 @@ func (e *Executor) describeNanoflow(name ast.QualifiedName) error { lines = append(lines, "END;") lines = append(lines, "/") - fmt.Fprintln(e.output, strings.Join(lines, "\n")) + fmt.Fprintln(ctx.Output, strings.Join(lines, "\n")) return nil } // describeMicroflowToString generates MDL source for a microflow and returns it as a string // along with a source map mapping node IDs to line ranges. -func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { +func describeMicroflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return "", nil, mdlerrors.NewBackend("build hierarchy", err) @@ -460,7 +466,7 @@ func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, ma } sourceMap := make(map[string]elkSourceRange) - mdl := e.renderMicroflowMDL(targetMf, name, entityNames, microflowNames, sourceMap) + mdl := renderMicroflowMDL(ctx, targetMf, name, entityNames, microflowNames, sourceMap) return mdl, sourceMap, nil } @@ -471,7 +477,8 @@ func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, ma // resolution; pass empty maps if unavailable (types will fall back to // "Object"/"List" stubs). If sourceMap is non-nil it will be populated with // ELK node IDs → line ranges for visualization; pass nil when not needed. -func (e *Executor) renderMicroflowMDL( +func renderMicroflowMDL( + ctx *ExecContext, mf *microflows.Microflow, name ast.QualifiedName, entityNames map[model.ID]string, @@ -498,7 +505,7 @@ func (e *Executor) renderMicroflowMDL( for i, param := range mf.Parameters { paramType := "Object" if param.Type != nil { - paramType = e.formatMicroflowDataType(param.Type, entityNames) + paramType = formatMicroflowDataType(ctx, param.Type, entityNames) } comma := "," if i == len(mf.Parameters)-1 { @@ -512,7 +519,7 @@ func (e *Executor) renderMicroflowMDL( } if mf.ReturnType != nil { - returnType := e.formatMicroflowDataType(mf.ReturnType, entityNames) + returnType := formatMicroflowDataType(ctx, mf.ReturnType, entityNames) if returnType != "Void" && returnType != "" { returnLine := fmt.Sprintf("RETURNS %s", returnType) if mf.ReturnVariableName != "" && mf.ReturnVariableName != "Variable" { @@ -528,9 +535,9 @@ func (e *Executor) renderMicroflowMDL( if mf.ObjectCollection != nil && len(mf.ObjectCollection.Objects) > 0 { var activityLines []string if sourceMap != nil { - activityLines = e.formatMicroflowActivitiesWithSourceMap(mf, entityNames, microflowNames, sourceMap, headerLineCount) + activityLines = formatMicroflowActivitiesWithSourceMap(ctx, mf, entityNames, microflowNames, sourceMap, headerLineCount) } else { - activityLines = e.formatMicroflowActivities(mf, entityNames, microflowNames) + activityLines = formatMicroflowActivities(ctx, mf, entityNames, microflowNames) } for _, line := range activityLines { lines = append(lines, " "+line) @@ -557,7 +564,7 @@ func (e *Executor) renderMicroflowMDL( } // formatMicroflowDataType formats a microflow data type for MDL output. -func (e *Executor) formatMicroflowDataType(dt microflows.DataType, entityNames map[model.ID]string) string { +func formatMicroflowDataType(ctx *ExecContext, dt microflows.DataType, entityNames map[model.ID]string) string { if dt == nil { return "Unknown" } @@ -610,7 +617,8 @@ func (e *Executor) formatMicroflowDataType(dt microflows.DataType, entityNames m } // formatMicroflowActivities generates MDL statements for microflow activities. -func (e *Executor) formatMicroflowActivities( +func formatMicroflowActivities( + ctx *ExecContext, mf *microflows.Microflow, entityNames map[model.ID]string, microflowNames map[model.ID]string, @@ -652,7 +660,7 @@ func (e *Executor) formatMicroflowActivities( } // Find the merge point for each split (where branches converge) - splitMergeMap := e.findSplitMergePoints(mf.ObjectCollection, activityMap) + splitMergeMap := findSplitMergePoints(ctx, mf.ObjectCollection, activityMap) // Traverse the flow graph recursively visited := make(map[model.ID]bool) @@ -660,7 +668,7 @@ func (e *Executor) formatMicroflowActivities( // Build annotation map for @annotation emission annotationsByTarget := buildAnnotationsByTarget(mf.ObjectCollection) - e.traverseFlow(startID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, &lines, 0, nil, 0, annotationsByTarget) + traverseFlow(ctx, startID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, &lines, 0, nil, 0, annotationsByTarget) return lines } @@ -668,7 +676,8 @@ func (e *Executor) formatMicroflowActivities( // formatMicroflowActivitiesWithSourceMap generates MDL statements and populates a source map // mapping ELK node IDs ("node-") to line ranges (0-indexed) in the full MDL output. // headerLineCount is the number of lines before the BEGIN body (to compute absolute line numbers). -func (e *Executor) formatMicroflowActivitiesWithSourceMap( +func formatMicroflowActivitiesWithSourceMap( + ctx *ExecContext, mf *microflows.Microflow, entityNames map[model.ID]string, microflowNames map[model.ID]string, @@ -707,19 +716,20 @@ func (e *Executor) formatMicroflowActivitiesWithSourceMap( } } - splitMergeMap := e.findSplitMergePoints(mf.ObjectCollection, activityMap) + splitMergeMap := findSplitMergePoints(ctx, mf.ObjectCollection, activityMap) visited := make(map[model.ID]bool) // Build annotation map for @annotation emission annotationsByTarget := buildAnnotationsByTarget(mf.ObjectCollection) - e.traverseFlow(startID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, &lines, 0, sourceMap, headerLineCount, annotationsByTarget) + traverseFlow(ctx, startID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, &lines, 0, sourceMap, headerLineCount, annotationsByTarget) return lines } // findSplitMergePoints finds the corresponding merge point for each exclusive split. -func (e *Executor) findSplitMergePoints( +func findSplitMergePoints( + ctx *ExecContext, oc *microflows.MicroflowObjectCollection, activityMap map[model.ID]microflows.MicroflowObject, ) map[model.ID]model.ID { @@ -736,7 +746,7 @@ func (e *Executor) findSplitMergePoints( if _, ok := obj.(*microflows.ExclusiveSplit); ok { splitID := obj.GetID() // Find merge by following both branches until they converge - mergeID := e.findMergeForSplit(splitID, flowsByOrigin, activityMap) + mergeID := findMergeForSplit(ctx, splitID, flowsByOrigin, activityMap) if mergeID != "" { result[splitID] = mergeID } @@ -747,7 +757,8 @@ func (e *Executor) findSplitMergePoints( } // findMergeForSplit finds the ExclusiveMerge where branches from a split converge. -func (e *Executor) findMergeForSplit( +func findMergeForSplit( + ctx *ExecContext, splitID model.ID, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, activityMap map[model.ID]microflows.MicroflowObject, @@ -758,8 +769,8 @@ func (e *Executor) findMergeForSplit( } // Follow each branch and collect all reachable nodes - branch0Nodes := e.collectReachableNodes(flows[0].DestinationID, flowsByOrigin, activityMap, make(map[model.ID]bool)) - branch1Nodes := e.collectReachableNodes(flows[1].DestinationID, flowsByOrigin, activityMap, make(map[model.ID]bool)) + branch0Nodes := collectReachableNodes(ctx, flows[0].DestinationID, flowsByOrigin, activityMap, make(map[model.ID]bool)) + branch1Nodes := collectReachableNodes(ctx, flows[1].DestinationID, flowsByOrigin, activityMap, make(map[model.ID]bool)) // Find the first common node that is an ExclusiveMerge // This is a simplification - we look for the first merge point reachable from both branches @@ -775,7 +786,8 @@ func (e *Executor) findMergeForSplit( } // collectReachableNodes collects all nodes reachable from a starting node. -func (e *Executor) collectReachableNodes( +func collectReachableNodes( + ctx *ExecContext, startID model.ID, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, activityMap map[model.ID]microflows.MicroflowObject, @@ -799,3 +811,29 @@ func (e *Executor) collectReachableNodes( traverse(startID) return result } + +// --- Executor method wrappers for callers in unmigrated code --- + +func (e *Executor) showMicroflows(moduleName string) error { + return showMicroflows(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) showNanoflows(moduleName string) error { + return showNanoflows(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeMicroflow(name ast.QualifiedName) error { + return describeMicroflow(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeNanoflow(name ast.QualifiedName) error { + return describeNanoflow(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + return describeMicroflowToString(e.newExecContext(context.Background()), name) +} + +func (e *Executor) renderMicroflowMDL(mf *microflows.Microflow, name ast.QualifiedName, entityNames map[model.ID]string, microflowNames map[model.ID]string, sourceMap map[string]elkSourceRange) string { + return renderMicroflowMDL(e.newExecContext(context.Background()), mf, name, entityNames, microflowNames, sourceMap) +} diff --git a/mdl/executor/cmd_microflows_show_helpers.go b/mdl/executor/cmd_microflows_show_helpers.go index d32b5299..e539c975 100644 --- a/mdl/executor/cmd_microflows_show_helpers.go +++ b/mdl/executor/cmd_microflows_show_helpers.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -95,7 +96,8 @@ func emitObjectAnnotations(obj microflows.MicroflowObject, lines *[]string, inde // emitActivityStatement appends the formatted activity statement (with error handling) // to the lines slice. It handles ON ERROR CONTINUE/ROLLBACK suffixes and custom error // handler blocks. This replaces the copy-pasted error handling logic in each traversal function. -func (e *Executor) emitActivityStatement( +func emitActivityStatement( + ctx *ExecContext, obj microflows.MicroflowObject, stmt string, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, @@ -127,7 +129,8 @@ func (e *Executor) emitActivityStatement( suffix := formatErrorHandlingSuffix(errType) if errorHandlerFlow != nil && hasCustomErrorHandler(errType) { - errStmts := e.collectErrorHandlerStatements( + errStmts := collectErrorHandlerStatements( + ctx, errorHandlerFlow.DestinationID, activityMap, flowsByOrigin, entityNames, microflowNames, ) @@ -165,7 +168,8 @@ func recordSourceMap(sourceMap map[string]elkSourceRange, nodeID model.ID, start // traverseFlow recursively traverses the microflow graph and generates MDL statements. // When sourceMap is non-nil, it also records line ranges for each activity node. -func (e *Executor) traverseFlow( +func traverseFlow( + ctx *ExecContext, currentID model.ID, activityMap map[model.ID]microflows.MicroflowObject, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, @@ -195,7 +199,7 @@ func (e *Executor) traverseFlow( visited[currentID] = true - stmt := e.formatActivity(obj, entityNames, microflowNames) + stmt := formatActivity(ctx, obj, entityNames, microflowNames) indentStr := strings.Repeat(" ", indent) // Handle ExclusiveSplit specially - need to process both branches @@ -228,7 +232,7 @@ func (e *Executor) traverseFlow( } if isGuard { - e.traverseFlowUntilMerge(trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) *lines = append(*lines, indentStr+"END IF;") recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) @@ -242,11 +246,11 @@ func (e *Executor) traverseFlow( break } } - e.traverseFlow(contID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlow(ctx, contID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } else { if trueFlow != nil { - e.traverseFlowUntilMerge(trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) } if falseFlow != nil { @@ -255,7 +259,7 @@ func (e *Executor) traverseFlow( for id := range visited { visitedFalseBranch[id] = true } - e.traverseFlowUntilMerge(falseFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visitedFalseBranch, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, falseFlow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visitedFalseBranch, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) } *lines = append(*lines, indentStr+"END IF;") @@ -266,7 +270,7 @@ func (e *Executor) traverseFlow( visited[mergeID] = true nextFlows := flowsByOrigin[mergeID] for _, flow := range nextFlows { - e.traverseFlow(flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlow(ctx, flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } } @@ -281,7 +285,7 @@ func (e *Executor) traverseFlow( *lines = append(*lines, indentStr+stmt) } - e.emitLoopBody(loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + emitLoopBody(ctx, loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) *lines = append(*lines, indentStr+loopEndKeyword(loop)+";") recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) @@ -289,7 +293,7 @@ func (e *Executor) traverseFlow( // Continue after the loop flows := flowsByOrigin[currentID] for _, flow := range flows { - e.traverseFlow(flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlow(ctx, flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } return } @@ -297,18 +301,19 @@ func (e *Executor) traverseFlow( // Regular activity startLine := len(*lines) + headerLineCount normalFlows := findNormalFlows(flowsByOrigin[currentID]) - e.emitActivityStatement(obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) + emitActivityStatement(ctx, obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) // Follow normal (non-error-handler) outgoing flows for _, flow := range normalFlows { - e.traverseFlow(flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlow(ctx, flow.DestinationID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } // traverseFlowUntilMerge traverses the flow until reaching a merge point. // When sourceMap is non-nil, it also records line ranges for each activity node. -func (e *Executor) traverseFlowUntilMerge( +func traverseFlowUntilMerge( + ctx *ExecContext, currentID model.ID, mergeID model.ID, activityMap map[model.ID]microflows.MicroflowObject, @@ -336,14 +341,14 @@ func (e *Executor) traverseFlowUntilMerge( if _, isMerge := obj.(*microflows.ExclusiveMerge); isMerge { flows := flowsByOrigin[currentID] for _, flow := range flows { - e.traverseFlowUntilMerge(flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } return } visited[currentID] = true - stmt := e.formatActivity(obj, entityNames, microflowNames) + stmt := formatActivity(ctx, obj, entityNames, microflowNames) indentStr := strings.Repeat(" ", indent) // Handle nested ExclusiveSplit @@ -374,7 +379,7 @@ func (e *Executor) traverseFlowUntilMerge( } if isGuard { - e.traverseFlowUntilMerge(trueFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, trueFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) *lines = append(*lines, indentStr+"END IF;") recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) @@ -388,11 +393,11 @@ func (e *Executor) traverseFlowUntilMerge( break } } - e.traverseFlowUntilMerge(contID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, contID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } else { if trueFlow != nil { - e.traverseFlowUntilMerge(trueFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, trueFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) } if falseFlow != nil { @@ -401,7 +406,7 @@ func (e *Executor) traverseFlowUntilMerge( for id := range visited { visitedFalseBranch[id] = true } - e.traverseFlowUntilMerge(falseFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visitedFalseBranch, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, falseFlow.DestinationID, nestedMergeID, activityMap, flowsByOrigin, splitMergeMap, visitedFalseBranch, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) } *lines = append(*lines, indentStr+"END IF;") @@ -412,7 +417,7 @@ func (e *Executor) traverseFlowUntilMerge( visited[nestedMergeID] = true nextFlows := flowsByOrigin[nestedMergeID] for _, flow := range nextFlows { - e.traverseFlowUntilMerge(flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } } @@ -427,7 +432,7 @@ func (e *Executor) traverseFlowUntilMerge( *lines = append(*lines, indentStr+stmt) } - e.emitLoopBody(loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + emitLoopBody(ctx, loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) *lines = append(*lines, indentStr+loopEndKeyword(loop)+";") recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) @@ -435,7 +440,7 @@ func (e *Executor) traverseFlowUntilMerge( // Continue after the loop within the branch flows := flowsByOrigin[currentID] for _, flow := range flows { - e.traverseFlowUntilMerge(flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } return } @@ -443,18 +448,19 @@ func (e *Executor) traverseFlowUntilMerge( // Regular activity startLine := len(*lines) + headerLineCount normalFlows := findNormalFlows(flowsByOrigin[currentID]) - e.emitActivityStatement(obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) + emitActivityStatement(ctx, obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) // Follow normal (non-error-handler) outgoing flows until merge for _, flow := range normalFlows { - e.traverseFlowUntilMerge(flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } // traverseLoopBody traverses activities inside a loop body. // When sourceMap is non-nil, it also records line ranges for each activity node. -func (e *Executor) traverseLoopBody( +func traverseLoopBody( + ctx *ExecContext, currentID model.ID, activityMap map[model.ID]microflows.MicroflowObject, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, @@ -478,7 +484,7 @@ func (e *Executor) traverseLoopBody( visited[currentID] = true - stmt := e.formatActivity(obj, entityNames, microflowNames) + stmt := formatActivity(ctx, obj, entityNames, microflowNames) indentStr := strings.Repeat(" ", indent) // Handle nested LoopedActivity specially @@ -489,7 +495,7 @@ func (e *Executor) traverseLoopBody( *lines = append(*lines, indentStr+stmt) } - e.emitLoopBody(loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + emitLoopBody(ctx, loop, flowsByOrigin, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) *lines = append(*lines, indentStr+loopEndKeyword(loop)+";") recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) @@ -498,7 +504,7 @@ func (e *Executor) traverseLoopBody( flows := flowsByOrigin[currentID] for _, flow := range flows { if _, inLoop := activityMap[flow.DestinationID]; inLoop { - e.traverseLoopBody(flow.DestinationID, activityMap, flowsByOrigin, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseLoopBody(ctx, flow.DestinationID, activityMap, flowsByOrigin, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } return @@ -507,20 +513,21 @@ func (e *Executor) traverseLoopBody( // Regular activity startLine := len(*lines) + headerLineCount normalFlows := findNormalFlows(flowsByOrigin[currentID]) - e.emitActivityStatement(obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) + emitActivityStatement(ctx, obj, stmt, flowsByOrigin, activityMap, entityNames, microflowNames, lines, indentStr, annotationsByTarget) recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1) // Follow normal (non-error-handler) outgoing flows within the loop body for _, flow := range normalFlows { if _, inLoop := activityMap[flow.DestinationID]; inLoop { - e.traverseLoopBody(flow.DestinationID, activityMap, flowsByOrigin, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) + traverseLoopBody(ctx, flow.DestinationID, activityMap, flowsByOrigin, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) } } } // emitLoopBody processes the inner objects of a LoopedActivity. // Shared by traverseFlow and traverseLoopBody for both top-level and nested loops. -func (e *Executor) emitLoopBody( +func emitLoopBody( + ctx *ExecContext, loop *microflows.LoopedActivity, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, entityNames map[model.ID]string, @@ -580,7 +587,7 @@ func (e *Executor) emitLoopBody( // Traverse the loop body if firstID != "" { loopVisited := make(map[model.ID]bool) - e.traverseLoopBody(firstID, loopActivityMap, loopFlowsByOrigin, loopVisited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) + traverseLoopBody(ctx, firstID, loopActivityMap, loopFlowsByOrigin, loopVisited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget) } } @@ -704,7 +711,8 @@ func getActionErrorHandlingType(activity *microflows.ActionActivity) microflows. // collectErrorHandlerStatements traverses the error handler flow and collects statements. // Returns a slice of MDL statements for the error handler block. -func (e *Executor) collectErrorHandlerStatements( +func collectErrorHandlerStatements( + ctx *ExecContext, startID model.ID, activityMap map[model.ID]microflows.MicroflowObject, flowsByOrigin map[model.ID][]*microflows.SequenceFlow, @@ -732,7 +740,7 @@ func (e *Executor) collectErrorHandlerStatements( visited[id] = true - stmt := e.formatActivity(obj, entityNames, microflowNames) + stmt := formatActivity(ctx, obj, entityNames, microflowNames) if stmt != "" { statements = append(statements, stmt) } @@ -756,3 +764,32 @@ func loopEndKeyword(loop *microflows.LoopedActivity) string { } return "END LOOP" } + +// --- Executor method wrappers for callers in unmigrated code and tests --- + +func (e *Executor) traverseFlow( + currentID model.ID, + activityMap map[model.ID]microflows.MicroflowObject, + flowsByOrigin map[model.ID][]*microflows.SequenceFlow, + splitMergeMap map[model.ID]model.ID, + visited map[model.ID]bool, + entityNames map[model.ID]string, + microflowNames map[model.ID]string, + lines *[]string, + indent int, + sourceMap map[string]elkSourceRange, + headerLineCount int, + annotationsByTarget map[model.ID][]string, +) { + traverseFlow(e.newExecContext(context.Background()), currentID, activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget) +} + +func (e *Executor) collectErrorHandlerStatements( + startID model.ID, + activityMap map[model.ID]microflows.MicroflowObject, + flowsByOrigin map[model.ID][]*microflows.SequenceFlow, + entityNames map[model.ID]string, + microflowNames map[model.ID]string, +) []string { + return collectErrorHandlerStatements(e.newExecContext(context.Background()), startID, activityMap, flowsByOrigin, entityNames, microflowNames) +} diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index a2f66ffa..5a11458e 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -86,10 +86,10 @@ func registerAssociationHandlers(r *Registry) { func registerMicroflowHandlers(r *Registry) { r.Register(&ast.CreateMicroflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateMicroflow(stmt.(*ast.CreateMicroflowStmt)) + return execCreateMicroflow(ctx, stmt.(*ast.CreateMicroflowStmt)) }) r.Register(&ast.DropMicroflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropMicroflow(stmt.(*ast.DropMicroflowStmt)) + return execDropMicroflow(ctx, stmt.(*ast.DropMicroflowStmt)) }) } From 152bce2d8574c8307262c4176b37c6a7513fb226 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:21:13 +0200 Subject: [PATCH 07/16] refactor: migrate page, snippet, layout, and alter-page handlers to free functions --- mdl/executor/cmd_alter_page.go | 28 +-- mdl/executor/cmd_layouts.go | 8 +- mdl/executor/cmd_page_wireframe.go | 37 ++-- mdl/executor/cmd_pages_builder.go | 33 ++- mdl/executor/cmd_pages_create_v3.go | 18 +- mdl/executor/cmd_pages_describe.go | 120 ++++++----- mdl/executor/cmd_pages_describe_output.go | 204 ++++++++++--------- mdl/executor/cmd_pages_describe_parse.go | 189 ++++++++--------- mdl/executor/cmd_pages_describe_pluggable.go | 123 +++++------ mdl/executor/cmd_pages_show.go | 8 +- mdl/executor/cmd_snippets.go | 8 +- mdl/executor/register_stubs.go | 10 +- 12 files changed, 436 insertions(+), 350 deletions(-) diff --git a/mdl/executor/cmd_alter_page.go b/mdl/executor/cmd_alter_page.go index 9a247af3..c4658f1a 100644 --- a/mdl/executor/cmd_alter_page.go +++ b/mdl/executor/cmd_alter_page.go @@ -16,7 +16,8 @@ import ( ) // execAlterPage handles ALTER PAGE/SNIPPET Module.Name { operations }. -func (e *Executor) execAlterPage(s *ast.AlterPageStmt) error { +func execAlterPage(ctx *ExecContext, s *ast.AlterPageStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -79,7 +80,7 @@ func (e *Executor) execAlterPage(s *ast.AlterPageStmt) error { return mdlerrors.NewBackend("SET", err) } case *ast.InsertWidgetOp: - if err := e.applyInsertWidgetWith(rawData, o, modName, containerID, findWidget); err != nil { + if err := applyInsertWidgetWith(ctx, rawData, o, modName, containerID, findWidget); err != nil { return mdlerrors.NewBackend("INSERT", err) } case *ast.DropWidgetOp: @@ -87,7 +88,7 @@ func (e *Executor) execAlterPage(s *ast.AlterPageStmt) error { return mdlerrors.NewBackend("DROP", err) } case *ast.ReplaceWidgetOp: - if err := e.applyReplaceWidgetWith(rawData, o, modName, containerID, findWidget); err != nil { + if err := applyReplaceWidgetWith(ctx, rawData, o, modName, containerID, findWidget); err != nil { return mdlerrors.NewBackend("REPLACE", err) } case *ast.AddVariableOp: @@ -121,7 +122,7 @@ func (e *Executor) execAlterPage(s *ast.AlterPageStmt) error { return mdlerrors.NewBackend("save modified "+strings.ToLower(containerType), err) } - fmt.Fprintf(e.output, "Altered %s %s\n", strings.ToLower(containerType), s.PageName.String()) + fmt.Fprintf(ctx.Output, "Altered %s %s\n", strings.ToLower(containerType), s.PageName.String()) return nil } @@ -1168,12 +1169,12 @@ func setPluggableWidgetProperty(widget bson.D, propName string, value interface{ // ============================================================================ // applyInsertWidget inserts new widgets before or after a target widget (page format). -func (e *Executor) applyInsertWidget(rawData bson.D, op *ast.InsertWidgetOp, moduleName string, moduleID model.ID) error { - return e.applyInsertWidgetWith(rawData, op, moduleName, moduleID, findBsonWidget) +func applyInsertWidget(ctx *ExecContext, rawData bson.D, op *ast.InsertWidgetOp, moduleName string, moduleID model.ID) error { + return applyInsertWidgetWith(ctx, rawData, op, moduleName, moduleID, findBsonWidget) } // applyInsertWidgetWith inserts new widgets using the given widget finder. -func (e *Executor) applyInsertWidgetWith(rawData bson.D, op *ast.InsertWidgetOp, moduleName string, moduleID model.ID, find widgetFinder) error { +func applyInsertWidgetWith(ctx *ExecContext, rawData bson.D, op *ast.InsertWidgetOp, moduleName string, moduleID model.ID, find widgetFinder) error { var result *bsonWidgetResult if op.Target.IsColumn() { result = findBsonColumn(rawData, op.Target.Widget, op.Target.Column, find) @@ -1195,7 +1196,7 @@ func (e *Executor) applyInsertWidgetWith(rawData bson.D, op *ast.InsertWidgetOp, entityCtx := findEnclosingEntityContext(rawData, op.Target.Widget) // Build new widget BSON from AST (pass rawData for page param + widget scope resolution) - newBsonWidgets, err := e.buildWidgetsBson(op.Widgets, moduleName, moduleID, entityCtx, rawData) + newBsonWidgets, err := buildWidgetsBson(ctx, op.Widgets, moduleName, moduleID, entityCtx, rawData) if err != nil { return mdlerrors.NewBackend("build widgets", err) } @@ -1256,12 +1257,12 @@ func applyDropWidgetWith(rawData bson.D, op *ast.DropWidgetOp, find widgetFinder // ============================================================================ // applyReplaceWidget replaces a widget with new widgets (page format). -func (e *Executor) applyReplaceWidget(rawData bson.D, op *ast.ReplaceWidgetOp, moduleName string, moduleID model.ID) error { - return e.applyReplaceWidgetWith(rawData, op, moduleName, moduleID, findBsonWidget) +func applyReplaceWidget(ctx *ExecContext, rawData bson.D, op *ast.ReplaceWidgetOp, moduleName string, moduleID model.ID) error { + return applyReplaceWidgetWith(ctx, rawData, op, moduleName, moduleID, findBsonWidget) } // applyReplaceWidgetWith replaces a widget using the given widget finder. -func (e *Executor) applyReplaceWidgetWith(rawData bson.D, op *ast.ReplaceWidgetOp, moduleName string, moduleID model.ID, find widgetFinder) error { +func applyReplaceWidgetWith(ctx *ExecContext, rawData bson.D, op *ast.ReplaceWidgetOp, moduleName string, moduleID model.ID, find widgetFinder) error { var result *bsonWidgetResult if op.Target.IsColumn() { result = findBsonColumn(rawData, op.Target.Widget, op.Target.Column, find) @@ -1283,7 +1284,7 @@ func (e *Executor) applyReplaceWidgetWith(rawData bson.D, op *ast.ReplaceWidgetO entityCtx := findEnclosingEntityContext(rawData, op.Target.Widget) // Build new widget BSON from AST (pass rawData for page param + widget scope resolution) - newBsonWidgets, err := e.buildWidgetsBson(op.NewWidgets, moduleName, moduleID, entityCtx, rawData) + newBsonWidgets, err := buildWidgetsBson(ctx, op.NewWidgets, moduleName, moduleID, entityCtx, rawData) if err != nil { return mdlerrors.NewBackend("build replacement widgets", err) } @@ -1532,7 +1533,8 @@ func applyDropVariable(rawData bson.D, op *ast.DropVariableOp) error { // Returns bson.D elements (not map[string]any) to preserve field ordering. // rawPageData is the full page/snippet BSON, used to extract page parameters // and existing widget IDs for PARAMETER/SELECTION DataSource resolution. -func (e *Executor) buildWidgetsBson(widgets []*ast.WidgetV3, moduleName string, moduleID model.ID, entityContext string, rawPageData bson.D) ([]any, error) { +func buildWidgetsBson(ctx *ExecContext, widgets []*ast.WidgetV3, moduleName string, moduleID model.ID, entityContext string, rawPageData bson.D) ([]any, error) { + e := ctx.executor paramScope, paramEntityNames := extractPageParamsFromBSON(rawPageData) widgetScope := extractWidgetScopeFromBSON(rawPageData) diff --git a/mdl/executor/cmd_layouts.go b/mdl/executor/cmd_layouts.go index bf02e62d..5b89572d 100644 --- a/mdl/executor/cmd_layouts.go +++ b/mdl/executor/cmd_layouts.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -12,7 +13,8 @@ import ( ) // showLayouts handles SHOW LAYOUTS command. -func (e *Executor) showLayouts(moduleName string) error { +func showLayouts(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -61,3 +63,7 @@ func (e *Executor) showLayouts(moduleName string) error { } return e.writeResult(result) } + +func (e *Executor) showLayouts(moduleName string) error { + return showLayouts(e.newExecContext(context.Background()), moduleName) +} diff --git a/mdl/executor/cmd_page_wireframe.go b/mdl/executor/cmd_page_wireframe.go index 48305885..d7e46fed 100644 --- a/mdl/executor/cmd_page_wireframe.go +++ b/mdl/executor/cmd_page_wireframe.go @@ -3,6 +3,7 @@ package executor import ( + "context" "encoding/json" "fmt" "strings" @@ -88,7 +89,8 @@ func (c *wireframeCounter) next() string { } // PageWireframeJSON generates wireframe JSON for a page. -func (e *Executor) PageWireframeJSON(name string) error { +func PageWireframeJSON(ctx *ExecContext, name string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -146,7 +148,7 @@ func (e *Executor) PageWireframeJSON(name string) error { if rawData != nil { if formCall, ok := rawData["FormCall"].(map[string]any); ok { if layoutID := extractBinaryID(formCall["Layout"]); layoutID != "" { - layoutName = e.resolveLayoutName(model.ID(layoutID)) + layoutName = resolveLayoutName(ctx, model.ID(layoutID)) } else if formName, ok := formCall["Form"].(string); ok && formName != "" { layoutName = formName } @@ -167,7 +169,7 @@ func (e *Executor) PageWireframeJSON(name string) error { } // Get widget tree - rawWidgets := e.getPageWidgetsFromRaw(foundPage.ID) + rawWidgets := getPageWidgetsFromRaw(ctx, foundPage.ID) // Convert to wireframe nodes counter := &wireframeCounter{} @@ -177,7 +179,7 @@ func (e *Executor) PageWireframeJSON(name string) error { } // Generate MDL source - mdlSource, sourceMap := e.pageToMdlString(qn, root) + mdlSource, sourceMap := pageToMdlString(ctx, qn, root) data := pageWireframeData{ Format: "wireframe", @@ -196,12 +198,13 @@ func (e *Executor) PageWireframeJSON(name string) error { return mdlerrors.NewBackend("marshal wireframe JSON", err) } - fmt.Fprint(e.output, string(jsonBytes)) + fmt.Fprint(ctx.Output, string(jsonBytes)) return nil } // SnippetWireframeJSON generates wireframe JSON for a snippet. -func (e *Executor) SnippetWireframeJSON(name string) error { +func SnippetWireframeJSON(ctx *ExecContext, name string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -240,7 +243,7 @@ func (e *Executor) SnippetWireframeJSON(name string) error { modName := h.GetModuleName(modID) qualifiedName := modName + "." + foundSnippet.Name - rawWidgets := e.getSnippetWidgetsFromRaw(foundSnippet.ID) + rawWidgets := getSnippetWidgetsFromRaw(ctx, foundSnippet.ID) counter := &wireframeCounter{} var root []wireframeNode @@ -260,7 +263,7 @@ func (e *Executor) SnippetWireframeJSON(name string) error { return mdlerrors.NewBackend("marshal wireframe JSON", err) } - fmt.Fprint(e.output, string(jsonBytes)) + fmt.Fprint(ctx.Output, string(jsonBytes)) return nil } @@ -480,12 +483,12 @@ func normalizeWidgetType(typeName string) string { } // pageToMdlString generates MDL source for the page using the output-swap technique. -func (e *Executor) pageToMdlString(name ast.QualifiedName, root []wireframeNode) (string, map[string]elkSourceRange) { +func pageToMdlString(ctx *ExecContext, name ast.QualifiedName, root []wireframeNode) (string, map[string]elkSourceRange) { var buf strings.Builder - origOutput := e.output - e.output = &buf - _ = e.describePage(name) - e.output = origOutput + origOutput := ctx.Output + ctx.Output = &buf + _ = describePage(ctx, name) + ctx.Output = origOutput mdlSource := buf.String() if mdlSource == "" { @@ -534,3 +537,11 @@ func mapWidgetToLines(nodes []wireframeNode, lines []string, sourceMap map[strin } } } + +func (e *Executor) PageWireframeJSON(name string) error { + return PageWireframeJSON(e.newExecContext(context.Background()), name) +} + +func (e *Executor) SnippetWireframeJSON(name string) error { + return SnippetWireframeJSON(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_pages_builder.go b/mdl/executor/cmd_pages_builder.go index b262734b..219fe931 100644 --- a/mdl/executor/cmd_pages_builder.go +++ b/mdl/executor/cmd_pages_builder.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "log" "strings" @@ -222,7 +223,8 @@ func (pb *pageBuilder) resolveEntity(entityRef ast.QualifiedName) (model.ID, err // getModuleID returns the module ID for any container by using the hierarchy. // Deprecated: prefer using getHierarchy().FindModuleID() directly. -func (e *Executor) getModuleID(containerID model.ID) model.ID { +func getModuleID(ctx *ExecContext, containerID model.ID) model.ID { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return containerID @@ -232,7 +234,8 @@ func (e *Executor) getModuleID(containerID model.ID) model.ID { // getModuleName returns the module name for a module ID. // Deprecated: prefer using getHierarchy().GetModuleName() directly. -func (e *Executor) getModuleName(moduleID model.ID) string { +func getModuleName(ctx *ExecContext, moduleID model.ID) string { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return "" @@ -334,7 +337,8 @@ func (pb *pageBuilder) createFolder(name string, containerID model.ID) (model.ID } // execDropPage handles DROP PAGE statement. -func (e *Executor) execDropPage(s *ast.DropPageStmt) error { +func execDropPage(ctx *ExecContext, s *ast.DropPageStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -345,13 +349,13 @@ func (e *Executor) execDropPage(s *ast.DropPageStmt) error { } for _, p := range pages { - modID := e.getModuleID(p.ContainerID) - modName := e.getModuleName(modID) + modID := getModuleID(ctx, p.ContainerID) + modName := getModuleName(ctx, modID) if modName == s.Name.Module && p.Name == s.Name.Name { if err := e.writer.DeletePage(p.ID); err != nil { return mdlerrors.NewBackend("delete page", err) } - fmt.Fprintf(e.output, "Dropped page %s\n", s.Name.String()) + fmt.Fprintf(ctx.Output, "Dropped page %s\n", s.Name.String()) return nil } } @@ -360,7 +364,8 @@ func (e *Executor) execDropPage(s *ast.DropPageStmt) error { } // execDropSnippet handles DROP SNIPPET statement. -func (e *Executor) execDropSnippet(s *ast.DropSnippetStmt) error { +func execDropSnippet(ctx *ExecContext, s *ast.DropSnippetStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -371,16 +376,24 @@ func (e *Executor) execDropSnippet(s *ast.DropSnippetStmt) error { } for _, snip := range snippets { - modID := e.getModuleID(snip.ContainerID) - modName := e.getModuleName(modID) + modID := getModuleID(ctx, snip.ContainerID) + modName := getModuleName(ctx, modID) if modName == s.Name.Module && snip.Name == s.Name.Name { if err := e.writer.DeleteSnippet(snip.ID); err != nil { return mdlerrors.NewBackend("delete snippet", err) } - fmt.Fprintf(e.output, "Dropped snippet %s\n", s.Name.String()) + fmt.Fprintf(ctx.Output, "Dropped snippet %s\n", s.Name.String()) return nil } } return mdlerrors.NewNotFound("snippet", s.Name.String()) } + +func (e *Executor) getModuleID(containerID model.ID) model.ID { + return getModuleID(e.newExecContext(context.Background()), containerID) +} + +func (e *Executor) getModuleName(moduleID model.ID) string { + return getModuleName(e.newExecContext(context.Background()), moduleID) +} diff --git a/mdl/executor/cmd_pages_create_v3.go b/mdl/executor/cmd_pages_create_v3.go index c1da37c8..eaea1d0c 100644 --- a/mdl/executor/cmd_pages_create_v3.go +++ b/mdl/executor/cmd_pages_create_v3.go @@ -15,7 +15,8 @@ import ( // ============================================================================ // execCreatePageV3 handles CREATE PAGE statement with V3 syntax. -func (e *Executor) execCreatePageV3(s *ast.CreatePageStmtV3) error { +func execCreatePageV3(ctx *ExecContext, s *ast.CreatePageStmtV3) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -40,8 +41,8 @@ func (e *Executor) execCreatePageV3(s *ast.CreatePageStmtV3) error { existingPages, _ := e.reader.ListPages() var pagesToDelete []model.ID for _, p := range existingPages { - modID := e.getModuleID(p.ContainerID) - modName := e.getModuleName(modID) + modID := getModuleID(ctx, p.ContainerID) + modName := getModuleName(ctx, modID) if modName == s.Name.Module && p.Name == s.Name.Name { if !s.IsReplace && !s.IsModify && len(pagesToDelete) == 0 { return mdlerrors.NewAlreadyExists("page", s.Name.String()) @@ -94,12 +95,13 @@ func (e *Executor) execCreatePageV3(s *ast.CreatePageStmtV3) error { // Invalidate hierarchy cache so the new page's container is visible e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created page %s\n", s.Name.String()) + fmt.Fprintf(ctx.Output, "Created page %s\n", s.Name.String()) return nil } // execCreateSnippetV3 handles CREATE SNIPPET statement with V3 syntax. -func (e *Executor) execCreateSnippetV3(s *ast.CreateSnippetStmtV3) error { +func execCreateSnippetV3(ctx *ExecContext, s *ast.CreateSnippetStmtV3) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -115,8 +117,8 @@ func (e *Executor) execCreateSnippetV3(s *ast.CreateSnippetStmtV3) error { existingSnippets, _ := e.reader.ListSnippets() var snippetsToDelete []model.ID for _, snip := range existingSnippets { - modID := e.getModuleID(snip.ContainerID) - modName := e.getModuleName(modID) + modID := getModuleID(ctx, snip.ContainerID) + modName := getModuleName(ctx, modID) if modName == s.Name.Module && snip.Name == s.Name.Name { if !s.IsReplace && !s.IsModify && len(snippetsToDelete) == 0 { return mdlerrors.NewAlreadyExists("snippet", s.Name.String()) @@ -162,6 +164,6 @@ func (e *Executor) execCreateSnippetV3(s *ast.CreateSnippetStmtV3) error { // Invalidate hierarchy cache so the new snippet's container is visible e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created snippet %s\n", s.Name.String()) + fmt.Fprintf(ctx.Output, "Created snippet %s\n", s.Name.String()) return nil } diff --git a/mdl/executor/cmd_pages_describe.go b/mdl/executor/cmd_pages_describe.go index bf2d4917..d093c401 100644 --- a/mdl/executor/cmd_pages_describe.go +++ b/mdl/executor/cmd_pages_describe.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strconv" "strings" @@ -20,7 +21,8 @@ import ( // ============================================================================ // describePage handles DESCRIBE PAGE command - outputs MDL V3 syntax. -func (e *Executor) describePage(name ast.QualifiedName) error { +func describePage(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -54,11 +56,11 @@ func (e *Executor) describePage(name ast.QualifiedName) error { // Output documentation if present if foundPage.Documentation != "" { lines := strings.Split(foundPage.Documentation, "\n") - fmt.Fprint(e.output, "/**\n") + fmt.Fprint(ctx.Output, "/**\n") for _, line := range lines { - fmt.Fprintf(e.output, " * %s\n", line) + fmt.Fprintf(ctx.Output, " * %s\n", line) } - fmt.Fprint(e.output, " */\n") + fmt.Fprint(ctx.Output, " */\n") } // Get title @@ -79,7 +81,7 @@ func (e *Executor) describePage(name ast.QualifiedName) error { if rawData != nil { if formCall, ok := rawData["FormCall"].(map[string]any); ok { if layoutID := extractBinaryID(formCall["Layout"]); layoutID != "" { - layoutName = e.resolveLayoutName(model.ID(layoutID)) + layoutName = resolveLayoutName(ctx, model.ID(layoutID)) } else if formName, ok := formCall["Form"].(string); ok && formName != "" { layoutName = formName } @@ -88,7 +90,7 @@ func (e *Executor) describePage(name ast.QualifiedName) error { // @excluded annotation if foundPage.Excluded { - fmt.Fprintln(e.output, "@excluded") + fmt.Fprintln(ctx.Output, "@excluded") } // V3 syntax: CREATE PAGE Module.Page (Title: '...', Layout: ..., Params: { }) @@ -135,15 +137,15 @@ func (e *Executor) describePage(name ast.QualifiedName) error { } // Output widgets from raw page data - rawWidgets := e.getPageWidgetsFromRaw(foundPage.ID) + rawWidgets := getPageWidgetsFromRaw(ctx, foundPage.ID) if len(rawWidgets) > 0 { - formatWidgetProps(e.output, "", header, props, " {\n") + formatWidgetProps(ctx.Output, "", header, props, " {\n") for _, w := range rawWidgets { - e.outputWidgetMDLV3(w, 1) + outputWidgetMDLV3(ctx, w, 1) } - fmt.Fprint(e.output, "}") + fmt.Fprint(ctx.Output, "}") } else { - formatWidgetProps(e.output, "", header, props, "") + formatWidgetProps(ctx.Output, "", header, props, "") } // Add GRANT VIEW if roles are assigned @@ -152,11 +154,11 @@ func (e *Executor) describePage(name ast.QualifiedName) error { for i, r := range foundPage.AllowedRoles { roles[i] = string(r) } - fmt.Fprintf(e.output, "\n\nGRANT VIEW ON PAGE %s.%s TO %s;", + fmt.Fprintf(ctx.Output, "\n\nGRANT VIEW ON PAGE %s.%s TO %s;", modName, foundPage.Name, strings.Join(roles, ", ")) } - fmt.Fprint(e.output, "\n") + fmt.Fprint(ctx.Output, "\n") return nil } @@ -175,7 +177,8 @@ func formatParametersV3(params []string) []string { } // describeSnippet handles DESCRIBE SNIPPET command - outputs MDL V3 syntax. -func (e *Executor) describeSnippet(name ast.QualifiedName) error { +func describeSnippet(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -209,11 +212,11 @@ func (e *Executor) describeSnippet(name ast.QualifiedName) error { // Output documentation if present if foundSnippet.Documentation != "" { lines := strings.Split(foundSnippet.Documentation, "\n") - fmt.Fprint(e.output, "/**\n") + fmt.Fprint(ctx.Output, "/**\n") for _, line := range lines { - fmt.Fprintf(e.output, " * %s\n", line) + fmt.Fprintf(ctx.Output, " * %s\n", line) } - fmt.Fprint(e.output, " */\n") + fmt.Fprint(ctx.Output, " */\n") } // Get raw data to check for parameters @@ -224,7 +227,7 @@ func (e *Executor) describeSnippet(name ast.QualifiedName) error { } // Output CREATE SNIPPET statement (V3 syntax) - fmt.Fprintf(e.output, "CREATE OR MODIFY SNIPPET %s.%s", modName, foundSnippet.Name) + fmt.Fprintf(ctx.Output, "CREATE OR MODIFY SNIPPET %s.%s", modName, foundSnippet.Name) folderPath := h.BuildFolderPath(foundSnippet.ContainerID) if len(params) > 0 || folderPath != "" { snippetProps := []string{} @@ -240,25 +243,26 @@ func (e *Executor) describeSnippet(name ast.QualifiedName) error { if folderPath != "" { snippetProps = append(snippetProps, fmt.Sprintf("Folder: %s", mdlQuote(folderPath))) } - fmt.Fprintf(e.output, " (%s)", strings.Join(snippetProps, ", ")) + fmt.Fprintf(ctx.Output, " (%s)", strings.Join(snippetProps, ", ")) } // Output widgets from raw snippet data - rawWidgets := e.getSnippetWidgetsFromRaw(foundSnippet.ID) + rawWidgets := getSnippetWidgetsFromRaw(ctx, foundSnippet.ID) if len(rawWidgets) > 0 { - fmt.Fprint(e.output, " {\n") + fmt.Fprint(ctx.Output, " {\n") for _, w := range rawWidgets { - e.outputWidgetMDLV3(w, 1) + outputWidgetMDLV3(ctx, w, 1) } - fmt.Fprint(e.output, "}") + fmt.Fprint(ctx.Output, "}") } - fmt.Fprint(e.output, "\n") + fmt.Fprint(ctx.Output, "\n") return nil } // describeLayout handles DESCRIBE LAYOUT command - outputs MDL-style representation. -func (e *Executor) describeLayout(name ast.QualifiedName) error { +func describeLayout(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -292,11 +296,11 @@ func (e *Executor) describeLayout(name ast.QualifiedName) error { // Output documentation if present if foundLayout.Documentation != "" { lines := strings.Split(foundLayout.Documentation, "\n") - fmt.Fprint(e.output, "/**\n") + fmt.Fprint(ctx.Output, "/**\n") for _, line := range lines { - fmt.Fprintf(e.output, " * %s\n", line) + fmt.Fprintf(ctx.Output, " * %s\n", line) } - fmt.Fprint(e.output, " */\n") + fmt.Fprint(ctx.Output, " */\n") } // Output layout type comment @@ -305,28 +309,29 @@ func (e *Executor) describeLayout(name ast.QualifiedName) error { layoutTypeStr = "Responsive" } - fmt.Fprintf(e.output, "-- Layout Type: %s\n", layoutTypeStr) - fmt.Fprintf(e.output, "-- This is a layout document. Layouts define the structure that pages are built upon.\n") - fmt.Fprintf(e.output, "-- Layouts cannot be created via MDL; they must be created in Studio Pro.\n\n") + fmt.Fprintf(ctx.Output, "-- Layout Type: %s\n", layoutTypeStr) + fmt.Fprintf(ctx.Output, "-- This is a layout document. Layouts define the structure that pages are built upon.\n") + fmt.Fprintf(ctx.Output, "-- Layouts cannot be created via MDL; they must be created in Studio Pro.\n\n") // Output as a comment showing the layout name - fmt.Fprintf(e.output, "-- LAYOUT %s.%s\n", modName, foundLayout.Name) + fmt.Fprintf(ctx.Output, "-- LAYOUT %s.%s\n", modName, foundLayout.Name) // Output widgets from raw layout data - rawWidgets := e.getLayoutWidgetsFromRaw(foundLayout.ID) + rawWidgets := getLayoutWidgetsFromRaw(ctx, foundLayout.ID) if len(rawWidgets) > 0 { - fmt.Fprint(e.output, "-- Widget structure:\n") + fmt.Fprint(ctx.Output, "-- Widget structure:\n") for _, w := range rawWidgets { - e.outputWidgetMDLV3Comment(w, 0) + outputWidgetMDLV3Comment(ctx, w, 0) } } - fmt.Fprint(e.output, "\n") + fmt.Fprint(ctx.Output, "\n") return nil } // getLayoutWidgetsFromRaw extracts widgets from raw layout BSON. -func (e *Executor) getLayoutWidgetsFromRaw(layoutID model.ID) []rawWidget { +func getLayoutWidgetsFromRaw(ctx *ExecContext, layoutID model.ID) []rawWidget { + e := ctx.executor // Get raw layout data rawData, err := e.reader.GetRawUnit(layoutID) if err != nil { @@ -339,22 +344,23 @@ func (e *Executor) getLayoutWidgetsFromRaw(layoutID model.ID) []rawWidget { return nil } - return e.parseRawWidget(widgetData) + return parseRawWidget(ctx, widgetData) } // outputWidgetMDLV3Comment outputs a widget as MDL V3 comment. -func (e *Executor) outputWidgetMDLV3Comment(w rawWidget, indent int) { +func outputWidgetMDLV3Comment(ctx *ExecContext, w rawWidget, indent int) { prefix := strings.Repeat(" ", indent) - fmt.Fprintf(e.output, "%s-- %s %s\n", prefix, w.Type, w.Name) + fmt.Fprintf(ctx.Output, "%s-- %s %s\n", prefix, w.Type, w.Name) // Output children for _, child := range w.Children { - e.outputWidgetMDLV3Comment(child, indent+1) + outputWidgetMDLV3Comment(ctx, child, indent+1) } } // getSnippetWidgetsFromRaw extracts widgets from raw snippet BSON. -func (e *Executor) getSnippetWidgetsFromRaw(snippetID model.ID) []rawWidget { +func getSnippetWidgetsFromRaw(ctx *ExecContext, snippetID model.ID) []rawWidget { + e := ctx.executor // Get raw snippet data rawData, err := e.reader.GetRawUnit(snippetID) if err != nil { @@ -377,7 +383,7 @@ func (e *Executor) getSnippetWidgetsFromRaw(snippetID model.ID) []rawWidget { var result []rawWidget for _, w := range widgetsArray { if wMap, ok := w.(map[string]any); ok { - result = append(result, e.parseRawWidget(wMap)...) + result = append(result, parseRawWidget(ctx, wMap)...) } } return result @@ -432,7 +438,8 @@ func getBsonArrayMaps(v any) []map[string]any { } // resolveLayoutName resolves a layout ID to its qualified name. -func (e *Executor) resolveLayoutName(layoutID model.ID) string { +func resolveLayoutName(ctx *ExecContext, layoutID model.ID) string { + e := ctx.executor layouts, err := e.reader.ListLayouts() if err != nil { return string(layoutID) @@ -610,7 +617,8 @@ func getBsonArrayElements(v any) []any { } // getPageWidgetsFromRaw extracts widgets from raw page BSON. -func (e *Executor) getPageWidgetsFromRaw(pageID model.ID) []rawWidget { +func getPageWidgetsFromRaw(ctx *ExecContext, pageID model.ID) []rawWidget { + e := ctx.executor // Get raw page data rawData, err := e.reader.GetRawUnit(pageID) if err != nil { @@ -638,7 +646,7 @@ func (e *Executor) getPageWidgetsFromRaw(pageID model.ID) []rawWidget { argWidgets := getBsonArrayElements(argMap["Widgets"]) for _, w := range argWidgets { if wMap, ok := w.(map[string]any); ok { - parsed := e.parseRawWidget(wMap) + parsed := parseRawWidget(ctx, wMap) for _, pw := range parsed { // Unwrap the conditionalVisibilityWidget wrapper that // mxcli (and Studio Pro) adds as a layout placeholder @@ -753,3 +761,23 @@ func pageParamTypeMDL(p *pages.PageParameter) string { } return string(p.EntityID) } + +func (e *Executor) describeLayout(name ast.QualifiedName) error { + return describeLayout(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describePage(name ast.QualifiedName) error { + return describePage(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeSnippet(name ast.QualifiedName) error { + return describeSnippet(e.newExecContext(context.Background()), name) +} + +func (e *Executor) getPageWidgetsFromRaw(pageID model.ID) []rawWidget { + return getPageWidgetsFromRaw(e.newExecContext(context.Background()), pageID) +} + +func (e *Executor) getSnippetWidgetsFromRaw(snippetID model.ID) []rawWidget { + return getSnippetWidgetsFromRaw(e.newExecContext(context.Background()), snippetID) +} diff --git a/mdl/executor/cmd_pages_describe_output.go b/mdl/executor/cmd_pages_describe_output.go index 9345f8e0..84c27ac5 100644 --- a/mdl/executor/cmd_pages_describe_output.go +++ b/mdl/executor/cmd_pages_describe_output.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "io" "strings" @@ -124,7 +125,7 @@ func outputDataContainerContext(w io.Writer, prefix string, widgetName string, e // outputWidgetMDLV3 outputs a widget in MDL V3 syntax. // V3 syntax uses WIDGET Name (Props) { children } format. -func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { +func outputWidgetMDLV3(ctx *ExecContext, w rawWidget, indent int) { prefix := strings.Repeat(" ", indent) switch w.Type { @@ -132,13 +133,13 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { header := fmt.Sprintf("CONTAINER %s", w.Name) props := appendAppearanceProps(nil, w) if len(w.Children) > 0 { - formatWidgetProps(e.output, prefix, header, props, " {\n") + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } case "Forms$GroupBox", "Pages$GroupBox": @@ -162,13 +163,13 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { } props = appendAppearanceProps(props, w) if len(w.Children) > 0 { - formatWidgetProps(e.output, prefix, header, props, " {\n") + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } case "Forms$LayoutGrid", "Pages$LayoutGrid": @@ -177,9 +178,9 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { header += " " + w.Name } props := appendAppearanceProps(nil, w) - formatWidgetProps(e.output, prefix, header, props, " {\n") + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") for rowIdx, row := range w.Rows { - fmt.Fprintf(e.output, "%s ROW row%d {\n", prefix, rowIdx+1) + fmt.Fprintf(ctx.Output, "%s ROW row%d {\n", prefix, rowIdx+1) for colIdx, col := range row.Columns { var colProps []string widthStr := "AutoFill" @@ -193,15 +194,15 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { if col.PhoneWidth > 0 && col.PhoneWidth <= 12 { colProps = append(colProps, fmt.Sprintf("PhoneWidth: %d", col.PhoneWidth)) } - fmt.Fprintf(e.output, "%s COLUMN col%d (%s) {\n", prefix, colIdx+1, strings.Join(colProps, ", ")) + fmt.Fprintf(ctx.Output, "%s COLUMN col%d (%s) {\n", prefix, colIdx+1, strings.Join(colProps, ", ")) for _, cw := range col.Widgets { - e.outputWidgetMDLV3(cw, indent+3) + outputWidgetMDLV3(ctx, cw, indent+3) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) case "Forms$DynamicText", "Pages$DynamicText": header := fmt.Sprintf("DYNAMICTEXT %s", w.Name) @@ -216,7 +217,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("ContentParams: [%s]", strings.Join(formatParametersV3(w.Parameters), ", "))) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$ActionButton", "Pages$ActionButton": header := fmt.Sprintf("ACTIONBUTTON %s", w.Name) @@ -234,7 +235,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("ButtonStyle: %s", w.ButtonStyle)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$Text", "Pages$Text": props := []string{} @@ -242,7 +243,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Content: %s", mdlQuote(w.Content))) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, "STATICTEXT", props, "\n") + formatWidgetProps(ctx.Output, prefix, "STATICTEXT", props, "\n") case "Forms$Title", "Pages$Title": header := fmt.Sprintf("TITLE %s", w.Name) @@ -251,7 +252,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Content: %s", mdlQuote(w.Caption))) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$DataView", "Pages$DataView": header := fmt.Sprintf("DATAVIEW %s", w.Name) @@ -267,12 +268,12 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { } } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, " {\n") - outputDataContainerContext(e.output, prefix+" ", w.Name, w.EntityContext, false) + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") + outputDataContainerContext(ctx.Output, prefix+" ", w.Name, w.EntityContext, false) for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) case "Forms$TextBox", "Pages$TextBox": header := fmt.Sprintf("TEXTBOX %s", w.Name) @@ -284,7 +285,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Attribute: %s", w.Content)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$TextArea", "Pages$TextArea": header := fmt.Sprintf("TEXTAREA %s", w.Name) @@ -296,7 +297,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Attribute: %s", w.Content)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$DatePicker", "Pages$DatePicker": header := fmt.Sprintf("DATEPICKER %s", w.Name) @@ -308,7 +309,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Attribute: %s", w.Content)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$RadioButtons", "Pages$RadioButtons": header := fmt.Sprintf("RADIOBUTTONS %s", w.Name) @@ -320,7 +321,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Attribute: %s", w.Content)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Forms$CheckBox", "Pages$CheckBox": header := fmt.Sprintf("CHECKBOX %s", w.Name) @@ -344,7 +345,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, "ShowLabel: No") } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "CustomWidgets$CustomWidget": widgetType := w.RenderMode // We stored widget type in RenderMode @@ -390,24 +391,24 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { // Output CONTROLBAR and columns as children hasContent := len(w.ControlBar) > 0 || len(w.DataGridColumns) > 0 if hasContent { - formatWidgetProps(e.output, prefix, header, props, " {\n") - outputDataContainerContext(e.output, prefix+" ", w.Name, w.EntityContext, true) + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") + outputDataContainerContext(ctx.Output, prefix+" ", w.Name, w.EntityContext, true) // Output CONTROLBAR section if control bar widgets present if len(w.ControlBar) > 0 { - fmt.Fprintf(e.output, "%s CONTROLBAR controlBar1 {\n", prefix) + fmt.Fprintf(ctx.Output, "%s CONTROLBAR controlBar1 {\n", prefix) for _, cb := range w.ControlBar { - e.outputWidgetMDLV3(cb, indent+2) + outputWidgetMDLV3(ctx, cb, indent+2) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } // Output columns — derive name from attribute or caption, fall back to col%d for i, col := range w.DataGridColumns { colName := deriveColumnName(col, i) - e.outputDataGrid2ColumnV3(prefix+" ", colName, col) + outputDataGrid2ColumnV3(ctx, prefix+" ", colName, col) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } } else if widgetType == "GALLERY" { // Handle Gallery specially with datasource, selection, filter and content widgets @@ -455,27 +456,27 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { // Output filter and content widgets hasContent := len(w.Children) > 0 || len(w.FilterWidgets) > 0 if hasContent { - formatWidgetProps(e.output, prefix, header, props, " {\n") - outputDataContainerContext(e.output, prefix+" ", w.Name, w.EntityContext, true) + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") + outputDataContainerContext(ctx.Output, prefix+" ", w.Name, w.EntityContext, true) // Output FILTER section if filter widgets present if len(w.FilterWidgets) > 0 { - fmt.Fprintf(e.output, "%s FILTER filter1 {\n", prefix) + fmt.Fprintf(ctx.Output, "%s FILTER filter1 {\n", prefix) for _, filter := range w.FilterWidgets { - e.outputWidgetMDLV3(filter, indent+2) + outputWidgetMDLV3(ctx, filter, indent+2) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } // Output TEMPLATE section if content widgets present if len(w.Children) > 0 { - fmt.Fprintf(e.output, "%s TEMPLATE template1 {\n", prefix) + fmt.Fprintf(ctx.Output, "%s TEMPLATE template1 {\n", prefix) for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+2) + outputWidgetMDLV3(ctx, child, indent+2) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } } else if widgetType == "IMAGE" { header := fmt.Sprintf("IMAGE %s", w.Name) @@ -515,7 +516,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { } props = appendConditionalProps(props, w) props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } else if len(w.ExplicitProperties) > 0 && w.WidgetID != "" { // Generic pluggable widget with explicit properties header := fmt.Sprintf("PLUGGABLEWIDGET '%s' %s", w.WidgetID, w.Name) @@ -527,7 +528,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("%s: %s", ep.Key, ep.Value)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } else { header := fmt.Sprintf("%s %s", widgetType, w.Name) props := []string{} @@ -558,11 +559,11 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("FilterType: %s", w.FilterExpression)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } case "Forms$NavigationList", "Pages$NavigationList": - fmt.Fprintf(e.output, "%sNAVIGATIONLIST %s {\n", prefix, w.Name) + fmt.Fprintf(ctx.Output, "%sNAVIGATIONLIST %s {\n", prefix, w.Name) for _, child := range w.Children { itemHeader := fmt.Sprintf("ITEM %s", child.Name) props := []string{} @@ -572,16 +573,16 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { if child.ButtonStyle != "" && child.ButtonStyle != "Default" { props = append(props, fmt.Sprintf("ButtonStyle: %s", child.ButtonStyle)) } - formatWidgetProps(e.output, prefix+" ", itemHeader, props, " {\n") + formatWidgetProps(ctx.Output, prefix+" ", itemHeader, props, " {\n") for _, cw := range child.Children { - e.outputWidgetMDLV3(cw, indent+2) + outputWidgetMDLV3(ctx, cw, indent+2) } - fmt.Fprintf(e.output, "%s }\n", prefix) + fmt.Fprintf(ctx.Output, "%s }\n", prefix) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) case "Forms$Label", "Pages$Label": - fmt.Fprintf(e.output, "%sSTATICTEXT (Content: %s)\n", prefix, mdlQuote(w.Content)) + fmt.Fprintf(ctx.Output, "%sSTATICTEXT (Content: %s)\n", prefix, mdlQuote(w.Content)) case "Forms$Gallery", "Pages$Gallery": header := fmt.Sprintf("GALLERY %s", w.Name) @@ -613,14 +614,14 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { } props = appendAppearanceProps(props, w) if len(w.Children) > 0 { - formatWidgetProps(e.output, prefix, header, props, " {\n") - outputDataContainerContext(e.output, prefix+" ", w.Name, w.EntityContext, true) + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") + outputDataContainerContext(ctx.Output, prefix+" ", w.Name, w.EntityContext, true) for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } case "Forms$SnippetCallWidget", "Pages$SnippetCallWidget": @@ -630,14 +631,14 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { props = append(props, fmt.Sprintf("Snippet: %s", w.Content)) } props = appendAppearanceProps(props, w) - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") case "Footer": - fmt.Fprintf(e.output, "%sFOOTER %s {\n", prefix, w.Name) + fmt.Fprintf(ctx.Output, "%sFOOTER %s {\n", prefix, w.Name) for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) case "Forms$ListView", "Pages$ListView": // ListView (also used for Gallery serialization) @@ -665,23 +666,23 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { } props = appendAppearanceProps(props, w) if len(w.Children) > 0 { - formatWidgetProps(e.output, prefix, header, props, " {\n") - outputDataContainerContext(e.output, prefix+" ", w.Name, w.EntityContext, true) + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") + outputDataContainerContext(ctx.Output, prefix+" ", w.Name, w.EntityContext, true) for _, child := range w.Children { - e.outputWidgetMDLV3(child, indent+1) + outputWidgetMDLV3(ctx, child, indent+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } default: // Output unknown widget type as comment - fmt.Fprintf(e.output, "%s-- %s", prefix, w.Type) + fmt.Fprintf(ctx.Output, "%s-- %s", prefix, w.Type) if w.Name != "" { - fmt.Fprintf(e.output, " (%s)", w.Name) + fmt.Fprintf(ctx.Output, " (%s)", w.Name) } - fmt.Fprint(e.output, "\n") + fmt.Fprint(ctx.Output, "\n") } } @@ -711,7 +712,7 @@ func deriveColumnName(col rawDataGridColumn, index int) string { } // outputDataGrid2ColumnV3 outputs a single DataGrid2 column in V3 MDL syntax. -func (e *Executor) outputDataGrid2ColumnV3(prefix, colName string, col rawDataGridColumn) { +func outputDataGrid2ColumnV3(ctx *ExecContext, prefix, colName string, col rawDataGridColumn) { // Build the main column properties var props []string if col.Attribute != "" { @@ -782,18 +783,18 @@ func (e *Executor) outputDataGrid2ColumnV3(prefix, colName string, col rawDataGr if hasContent { // Output column with content block - formatWidgetProps(e.output, prefix, header, props, " {\n") + formatWidgetProps(ctx.Output, prefix, header, props, " {\n") for _, widget := range col.ContentWidgets { - e.outputWidgetMDLV3(widget, len(prefix)/2+1) + outputWidgetMDLV3(ctx, widget, len(prefix)/2+1) } - fmt.Fprintf(e.output, "%s}\n", prefix) + fmt.Fprintf(ctx.Output, "%s}\n", prefix) } else { // Output simple column line - formatWidgetProps(e.output, prefix, header, props, "\n") + formatWidgetProps(ctx.Output, prefix, header, props, "\n") } } -func (e *Executor) extractTextContent(w map[string]any, field string) string { +func extractTextContent(ctx *ExecContext, w map[string]any, field string) string { content, ok := w[field].(map[string]any) if !ok { return "" @@ -818,34 +819,34 @@ func (e *Executor) extractTextContent(w map[string]any, field string) string { return "" } -func (e *Executor) extractButtonCaption(w map[string]any) string { +func extractButtonCaption(ctx *ExecContext, w map[string]any) string { // Try Caption first (legacy format) - if caption := e.extractTextContent(w, "Caption"); caption != "" { + if caption := extractTextContent(ctx, w, "Caption"); caption != "" { return caption } // Try CaptionTemplate (modern format used by ActionButton) - return e.extractTextContent(w, "CaptionTemplate") + return extractTextContent(ctx, w, "CaptionTemplate") } // extractButtonCaptionParameters extracts parameters from ActionButton caption. // Tries CaptionTemplate first (modern format), then Caption (legacy format). -func (e *Executor) extractButtonCaptionParameters(w map[string]any) []string { +func extractButtonCaptionParameters(ctx *ExecContext, w map[string]any) []string { // Try CaptionTemplate first (modern format used by ActionButton) - if params := e.extractClientTemplateParameters(w, "CaptionTemplate"); params != nil { + if params := extractClientTemplateParameters(ctx, w, "CaptionTemplate"); params != nil { return params } // Fall back to Caption (legacy format) - return e.extractClientTemplateParameters(w, "Caption") + return extractClientTemplateParameters(ctx, w, "Caption") } -func (e *Executor) extractButtonStyle(w map[string]any) string { +func extractButtonStyle(ctx *ExecContext, w map[string]any) string { if style, ok := w["ButtonStyle"].(string); ok { return style } return "Default" } -func (e *Executor) extractButtonAction(w map[string]any) string { +func extractButtonAction(ctx *ExecContext, w map[string]any) string { action, ok := w["Action"].(map[string]any) if !ok { // Try primitive.M type @@ -891,7 +892,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { if pageName, ok := pageSettings["Form"].(string); ok && pageName != "" { pageAction := "SHOW_PAGE " + pageName // Extract page parameters - params := e.extractPageParameters(pageSettings) + params := extractPageParameters(ctx, pageSettings) if params != "" { pageAction += "(" + params + ")" } @@ -905,7 +906,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { if formSettings, ok := action["FormSettings"].(map[string]any); ok { if pageName, ok := formSettings["Form"].(string); ok && pageName != "" { result := "SHOW_PAGE " + pageName - params := e.extractPageParameters(formSettings) + params := extractPageParameters(ctx, formSettings) if params != "" { result += "(" + params + ")" } @@ -915,7 +916,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { if pageSettings, ok := action["PageSettings"].(map[string]any); ok { if pageName, ok := pageSettings["Form"].(string); ok && pageName != "" { result := "SHOW_PAGE " + pageName - params := e.extractPageParameters(pageSettings) + params := extractPageParameters(ctx, pageSettings) if params != "" { result += "(" + params + ")" } @@ -924,7 +925,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { } // Fall back to Page field (binary ID from legacy serialization) if pageID := extractBinaryID(action["Page"]); pageID != "" { - pageName := e.getPageQualifiedName(model.ID(pageID)) + pageName := getPageQualifiedName(ctx, model.ID(pageID)) if pageName != "" { return "SHOW_PAGE " + pageName } @@ -936,7 +937,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { if mfName, ok := settings["Microflow"].(string); ok && mfName != "" { result := "CALL_MICROFLOW " + mfName // Extract parameter mappings - params := e.extractMicroflowParameters(settings) + params := extractMicroflowParameters(ctx, settings) if params != "" { result += "(" + params + ")" } @@ -948,7 +949,7 @@ func (e *Executor) extractButtonAction(w map[string]any) string { if nfName, ok := action["Nanoflow"].(string); ok && nfName != "" { result := "CALL_NANOFLOW " + nfName // Extract parameter mappings (directly in the action) - params := e.extractNanoflowParameters(action) + params := extractNanoflowParameters(ctx, action) if params != "" { result += "(" + params + ")" } @@ -966,7 +967,8 @@ func (e *Executor) extractButtonAction(w map[string]any) string { } // getPageQualifiedName resolves a page ID to its qualified name. -func (e *Executor) getPageQualifiedName(pageID model.ID) string { +func getPageQualifiedName(ctx *ExecContext, pageID model.ID) string { + e := ctx.executor if pageID == "" { return "" } @@ -989,7 +991,7 @@ func (e *Executor) getPageQualifiedName(pageID model.ID) string { // extractPageParameters extracts page parameter mappings from a FormSettings/PageSettings object. // Returns formatted string like "Product: $currentObject" or empty string if no params. -func (e *Executor) extractPageParameters(settings map[string]any) string { +func extractPageParameters(ctx *ExecContext, settings map[string]any) string { mappings := getBsonArrayElements(settings["ParameterMappings"]) if len(mappings) == 0 { return "" @@ -1051,7 +1053,7 @@ func (e *Executor) extractPageParameters(settings map[string]any) string { // extractMicroflowParameters extracts microflow parameter mappings from a MicroflowSettings object. // Returns formatted string like "Product = $currentObject" or empty string if no params. -func (e *Executor) extractMicroflowParameters(settings map[string]any) string { +func extractMicroflowParameters(ctx *ExecContext, settings map[string]any) string { mappings := getBsonArrayElements(settings["ParameterMappings"]) if len(mappings) == 0 { return "" @@ -1112,7 +1114,7 @@ func (e *Executor) extractMicroflowParameters(settings map[string]any) string { // extractNanoflowParameters extracts nanoflow parameter mappings from an action object. // Returns formatted string like "Product = $currentObject" or empty string if no params. -func (e *Executor) extractNanoflowParameters(action map[string]any) string { +func extractNanoflowParameters(ctx *ExecContext, action map[string]any) string { mappings := getBsonArrayElements(action["ParameterMappings"]) if len(mappings) == 0 { return "" @@ -1171,7 +1173,7 @@ func (e *Executor) extractNanoflowParameters(action map[string]any) string { return strings.Join(params, ", ") } -func (e *Executor) extractTextCaption(w map[string]any) string { +func extractTextCaption(ctx *ExecContext, w map[string]any) string { caption, ok := w["Caption"].(map[string]any) if !ok { return "" @@ -1190,7 +1192,7 @@ func (e *Executor) extractTextCaption(w map[string]any) string { } // extractClientTemplateParameters extracts parameter values from a ClientTemplate field (Content or Caption). -func (e *Executor) extractClientTemplateParameters(w map[string]any, fieldName string) []string { +func extractClientTemplateParameters(ctx *ExecContext, w map[string]any, fieldName string) []string { template, ok := w[fieldName].(map[string]any) if !ok { return nil @@ -1243,3 +1245,7 @@ func (e *Executor) extractClientTemplateParameters(w map[string]any, fieldName s } return result } + +func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { + outputWidgetMDLV3(e.newExecContext(context.Background()), w, indent) +} diff --git a/mdl/executor/cmd_pages_describe_parse.go b/mdl/executor/cmd_pages_describe_parse.go index abb1dd8b..dd9283b1 100644 --- a/mdl/executor/cmd_pages_describe_parse.go +++ b/mdl/executor/cmd_pages_describe_parse.go @@ -23,7 +23,7 @@ func extractConditionalSettings(widget *rawWidget, w map[string]any) { } } -func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...string) []rawWidget { +func parseRawWidget(ctx *ExecContext, w map[string]any, parentEntityContext ...string) []rawWidget { inheritedCtx := "" if len(parentEntityContext) > 0 { inheritedCtx = parentEntityContext[0] @@ -52,10 +52,10 @@ func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...strin if typeName == "Forms$GroupBox" || typeName == "Pages$GroupBox" { // Caption is stored as CaptionTemplate (Forms$ClientTemplate) if ct, ok := w["CaptionTemplate"].(map[string]any); ok { - widget.Caption = e.extractTextFromTemplate(ct) + widget.Caption = extractTextFromTemplate(ctx, ct) } else { // Fallback to legacy Caption field - widget.Caption = e.extractTextCaption(w) + widget.Caption = extractTextCaption(ctx, w) } if collapsible, ok := w["Collapsible"].(string); ok { widget.Collapsible = collapsible @@ -69,7 +69,7 @@ func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...strin if children != nil { for _, c := range children { if cMap, ok := c.(map[string]any); ok { - widget.Children = append(widget.Children, e.parseRawWidget(cMap, inheritedCtx)...) + widget.Children = append(widget.Children, parseRawWidget(ctx, cMap, inheritedCtx)...) } } } @@ -95,167 +95,167 @@ func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...strin switch typeName { case "Forms$LayoutGrid", "Pages$LayoutGrid": - widget.Rows = e.parseLayoutGridRows(w, inheritedCtx) + widget.Rows = parseLayoutGridRows(ctx, w, inheritedCtx) return []rawWidget{widget} case "Forms$DynamicText", "Pages$DynamicText": - widget.Content = e.extractTextContent(w, "Content") - widget.Parameters = e.extractClientTemplateParameters(w, "Content") + widget.Content = extractTextContent(ctx, w, "Content") + widget.Parameters = extractClientTemplateParameters(ctx, w, "Content") if rm, ok := w["RenderMode"].(string); ok { widget.RenderMode = rm } return []rawWidget{widget} case "Forms$ActionButton", "Pages$ActionButton": - widget.Caption = e.extractButtonCaption(w) - widget.Parameters = e.extractButtonCaptionParameters(w) - widget.ButtonStyle = e.extractButtonStyle(w) - widget.Action = e.extractButtonAction(w) + widget.Caption = extractButtonCaption(ctx, w) + widget.Parameters = extractButtonCaptionParameters(ctx, w) + widget.ButtonStyle = extractButtonStyle(ctx, w) + widget.Action = extractButtonAction(ctx, w) return []rawWidget{widget} case "Forms$Text", "Pages$Text": - widget.Content = e.extractTextCaption(w) + widget.Content = extractTextCaption(ctx, w) if rm, ok := w["RenderMode"].(string); ok { widget.RenderMode = rm } return []rawWidget{widget} case "Forms$Title", "Pages$Title": - widget.Caption = e.extractTextCaption(w) + widget.Caption = extractTextCaption(ctx, w) return []rawWidget{widget} case "Forms$DataView", "Pages$DataView": - widget.DataSource = e.extractDataViewDataSource(w) + widget.DataSource = extractDataViewDataSource(ctx, w) if widget.DataSource != nil && widget.DataSource.Reference != "" { widget.EntityContext = widget.DataSource.Reference } else if inheritedCtx != "" { widget.EntityContext = inheritedCtx } - widget.Children = e.parseDataViewChildren(w, widget.EntityContext) + widget.Children = parseDataViewChildren(ctx, w, widget.EntityContext) return []rawWidget{widget} case "Forms$TextBox", "Pages$TextBox": - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractAttributeRef(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractAttributeRef(ctx, w) return []rawWidget{widget} case "Forms$TextArea", "Pages$TextArea": - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractAttributeRef(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractAttributeRef(ctx, w) return []rawWidget{widget} case "Forms$DatePicker", "Pages$DatePicker": - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractAttributeRef(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractAttributeRef(ctx, w) return []rawWidget{widget} case "Forms$RadioButtons", "Pages$RadioButtons", "Forms$RadioButtonGroup", "Pages$RadioButtonGroup": widget.Type = "Forms$RadioButtons" // Normalize type - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractAttributeRef(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractAttributeRef(ctx, w) return []rawWidget{widget} case "Forms$CheckBox", "Pages$CheckBox": - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractAttributeRef(w) - widget.Editable = e.extractEditable(w) - widget.ReadOnlyStyle = e.extractReadOnlyStyle(w) - widget.ShowLabel = e.extractShowLabel(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractAttributeRef(ctx, w) + widget.Editable = extractEditable(ctx, w) + widget.ReadOnlyStyle = extractReadOnlyStyle(ctx, w) + widget.ShowLabel = extractShowLabel(ctx, w) return []rawWidget{widget} case "CustomWidgets$CustomWidget": - widget.Caption = e.extractLabelText(w) - widget.Content = e.extractCustomWidgetAttribute(w) - widget.RenderMode = e.extractCustomWidgetType(w) // Store widget type in RenderMode - widget.WidgetID = e.extractCustomWidgetID(w) + widget.Caption = extractLabelText(ctx, w) + widget.Content = extractCustomWidgetAttribute(ctx, w) + widget.RenderMode = extractCustomWidgetType(ctx, w) // Store widget type in RenderMode + widget.WidgetID = extractCustomWidgetID(ctx, w) // For ComboBox, extract datasource and association attribute for association mode. // In association mode the Attribute binding is stored as EntityRef (not AttributeRef), // so we must use extractCustomWidgetPropertyAssociation instead of the generic scan. if widget.RenderMode == "COMBOBOX" { - widget.DataSource = e.extractComboBoxDataSource(w) + widget.DataSource = extractComboBoxDataSource(ctx, w) if widget.DataSource != nil { - widget.Content = e.extractCustomWidgetPropertyAssociation(w, "attributeAssociation") - widget.CaptionAttribute = e.extractCustomWidgetPropertyAttributeRef(w, "optionsSourceAssociationCaptionAttribute") + widget.Content = extractCustomWidgetPropertyAssociation(ctx, w, "attributeAssociation") + widget.CaptionAttribute = extractCustomWidgetPropertyAttributeRef(ctx, w, "optionsSourceAssociationCaptionAttribute") } } // For DataGrid2, also extract datasource, columns, CONTROLBAR widgets, paging, and selection if widget.RenderMode == "DATAGRID2" { - widget.DataSource = e.extractDataGrid2DataSource(w) - widget.PageSize = e.extractCustomWidgetPropertyString(w, "pageSize") - widget.Pagination = e.extractCustomWidgetPropertyString(w, "pagination") - widget.PagingPosition = e.extractCustomWidgetPropertyString(w, "pagingPosition") - widget.ShowPagingButtons = e.extractCustomWidgetPropertyString(w, "showPagingButtons") + widget.DataSource = extractDataGrid2DataSource(ctx, w) + widget.PageSize = extractCustomWidgetPropertyString(ctx, w, "pageSize") + widget.Pagination = extractCustomWidgetPropertyString(ctx, w, "pagination") + widget.PagingPosition = extractCustomWidgetPropertyString(ctx, w, "pagingPosition") + widget.ShowPagingButtons = extractCustomWidgetPropertyString(ctx, w, "showPagingButtons") // showNumberOfRows: not yet fully supported in DataGrid2, skip to avoid CE0463 - widget.Selection = e.extractGallerySelection(w) + widget.Selection = extractGallerySelection(ctx, w) if widget.DataSource != nil && widget.DataSource.Reference != "" { widget.EntityContext = widget.DataSource.Reference } else if inheritedCtx != "" { widget.EntityContext = inheritedCtx } - widget.DataGridColumns = e.extractDataGrid2Columns(w, widget.EntityContext) - widget.ControlBar = e.extractDataGrid2ControlBar(w) + widget.DataGridColumns = extractDataGrid2Columns(ctx, w, widget.EntityContext) + widget.ControlBar = extractDataGrid2ControlBar(ctx, w) } // For Gallery, extract datasource, content widgets, filter widgets, and selection mode if widget.RenderMode == "GALLERY" { - widget.DataSource = e.extractGalleryDataSource(w) - widget.Selection = e.extractGallerySelection(w) - widget.DesktopColumns = e.extractCustomWidgetPropertyString(w, "desktopItems") - widget.TabletColumns = e.extractCustomWidgetPropertyString(w, "tabletItems") - widget.PhoneColumns = e.extractCustomWidgetPropertyString(w, "phoneItems") + widget.DataSource = extractGalleryDataSource(ctx, w) + widget.Selection = extractGallerySelection(ctx, w) + widget.DesktopColumns = extractCustomWidgetPropertyString(ctx, w, "desktopItems") + widget.TabletColumns = extractCustomWidgetPropertyString(ctx, w, "tabletItems") + widget.PhoneColumns = extractCustomWidgetPropertyString(ctx, w, "phoneItems") if widget.DataSource != nil && widget.DataSource.Reference != "" { widget.EntityContext = widget.DataSource.Reference } else if inheritedCtx != "" { widget.EntityContext = inheritedCtx } - widget.Children = e.extractGalleryContent(w, widget.EntityContext) - widget.FilterWidgets = e.extractGalleryFilters(w) + widget.Children = extractGalleryContent(ctx, w, widget.EntityContext) + widget.FilterWidgets = extractGalleryFilters(ctx, w) } // For filter widgets, extract filter attributes and expression if widget.RenderMode == "TEXTFILTER" || widget.RenderMode == "NUMBERFILTER" || widget.RenderMode == "DROPDOWNFILTER" || widget.RenderMode == "DATEFILTER" { - widget.FilterAttributes = e.extractFilterAttributes(w) - widget.FilterExpression = e.extractFilterExpression(w) + widget.FilterAttributes = extractFilterAttributes(ctx, w) + widget.FilterExpression = extractFilterExpression(ctx, w) } // For pluggable Image widget, extract image-specific properties if widget.RenderMode == "IMAGE" { - e.extractImageProperties(w, &widget) + extractImageProperties(ctx, w, &widget) } // For generic pluggable widgets (not handled by dedicated extractors above), // extract all non-default properties as explicit key-value pairs. if !isKnownCustomWidgetType(widget.RenderMode) { - widget.ExplicitProperties = e.extractExplicitProperties(w) + widget.ExplicitProperties = extractExplicitProperties(ctx, w) } return []rawWidget{widget} case "Forms$Label", "Pages$Label": - widget.Content = e.extractTextCaption(w) + widget.Content = extractTextCaption(ctx, w) return []rawWidget{widget} case "Forms$NavigationList", "Pages$NavigationList": - widget.Children = e.parseNavigationListItems(w) + widget.Children = parseNavigationListItems(ctx, w) return []rawWidget{widget} case "Forms$Gallery", "Pages$Gallery": - widget.DataSource = e.extractGalleryDataSource(w) + widget.DataSource = extractGalleryDataSource(ctx, w) if widget.DataSource != nil && widget.DataSource.Reference != "" { widget.EntityContext = widget.DataSource.Reference } else if inheritedCtx != "" { widget.EntityContext = inheritedCtx } - widget.Children = e.parseGalleryContent(w, widget.EntityContext) + widget.Children = parseGalleryContent(ctx, w, widget.EntityContext) return []rawWidget{widget} case "Forms$SnippetCallWidget", "Pages$SnippetCallWidget": - widget.Content = e.extractSnippetRef(w) + widget.Content = extractSnippetRef(ctx, w) return []rawWidget{widget} case "Forms$ListView", "Pages$ListView": - widget.DataSource = e.extractListViewDataSource(w) + widget.DataSource = extractListViewDataSource(ctx, w) if widget.DataSource != nil && widget.DataSource.Reference != "" { widget.EntityContext = widget.DataSource.Reference } else if inheritedCtx != "" { widget.EntityContext = inheritedCtx } - widget.Children = e.parseListViewContent(w, widget.EntityContext) + widget.Children = parseListViewContent(ctx, w, widget.EntityContext) return []rawWidget{widget} default: @@ -264,10 +264,10 @@ func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...strin } } -func (e *Executor) parseLayoutGridRows(w map[string]any, entityContext ...string) []rawWidgetRow { - ctx := "" +func parseLayoutGridRows(ctx *ExecContext, w map[string]any, entityContext ...string) []rawWidgetRow { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } rows := getBsonArrayElements(w["Rows"]) if rows == nil { @@ -304,7 +304,7 @@ func (e *Executor) parseLayoutGridRows(w map[string]any, entityContext ...string colWidgets := getBsonArrayElements(cMap["Widgets"]) for _, cw := range colWidgets { if cwMap, ok := cw.(map[string]any); ok { - col.Widgets = append(col.Widgets, e.parseRawWidget(cwMap, ctx)...) + col.Widgets = append(col.Widgets, parseRawWidget(ctx, cwMap, entCtx)...) } } row.Columns = append(row.Columns, col) @@ -315,7 +315,7 @@ func (e *Executor) parseLayoutGridRows(w map[string]any, entityContext ...string } // parseNavigationListItems extracts items from a NavigationList widget. -func (e *Executor) parseNavigationListItems(w map[string]any) []rawWidget { +func parseNavigationListItems(ctx *ExecContext, w map[string]any) []rawWidget { items := getBsonArrayElements(w["Items"]) if items == nil { return nil @@ -342,12 +342,12 @@ func (e *Executor) parseNavigationListItems(w map[string]any) []rawWidget { if !ok { continue } - parsed := e.parseRawWidget(wMap) + parsed := parseRawWidget(ctx, wMap) rw.Children = append(rw.Children, parsed...) } // Extract action - rw.Action = e.extractNavigationListItemAction(itemMap) + rw.Action = extractNavigationListItemAction(ctx, itemMap) // Extract style from Appearance class if appearance, ok := itemMap["Appearance"].(map[string]any); ok { @@ -364,7 +364,7 @@ func (e *Executor) parseNavigationListItems(w map[string]any) []rawWidget { // extractNavigationListItemAction extracts action from a NavigationListItem. // NavigationListItem uses Forms$FormAction with FormSettings.Form for page references, // which differs from ActionButton's action format. -func (e *Executor) extractNavigationListItemAction(w map[string]any) string { +func extractNavigationListItemAction(ctx *ExecContext, w map[string]any) string { action, ok := w["Action"].(map[string]any) if !ok { return "" @@ -386,7 +386,7 @@ func (e *Executor) extractNavigationListItemAction(w map[string]any) string { } // Fall back to Page field (binary ID from mxcli serialization) if pageID := extractBinaryID(action["Page"]); pageID != "" { - pageName := e.getPageQualifiedName(model.ID(pageID)) + pageName := getPageQualifiedName(ctx, model.ID(pageID)) if pageName != "" { return "SHOW_PAGE '" + pageName + "'" } @@ -394,16 +394,16 @@ func (e *Executor) extractNavigationListItemAction(w map[string]any) string { return "SHOW_PAGE" default: // Delegate to the standard action extractor - return e.extractButtonAction(w) + return extractButtonAction(ctx, w) } } // parseDataViewChildren extracts child widgets from a DataView. // entityContext is the resolved entity context from the enclosing data container. -func (e *Executor) parseDataViewChildren(w map[string]any, entityContext ...string) []rawWidget { - ctx := "" +func parseDataViewChildren(ctx *ExecContext, w map[string]any, entityContext ...string) []rawWidget { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } var result []rawWidget @@ -411,7 +411,7 @@ func (e *Executor) parseDataViewChildren(w map[string]any, entityContext ...stri widgets := getBsonArrayElements(w["Widgets"]) for _, child := range widgets { if childMap, ok := child.(map[string]any); ok { - result = append(result, e.parseRawWidget(childMap, ctx)...) + result = append(result, parseRawWidget(ctx, childMap, entCtx)...) } } @@ -422,7 +422,7 @@ func (e *Executor) parseDataViewChildren(w map[string]any, entityContext ...stri footer := rawWidget{Type: "Footer", Name: "footer1"} for _, child := range footerWidgets { if childMap, ok := child.(map[string]any); ok { - footer.Children = append(footer.Children, e.parseRawWidget(childMap, ctx)...) + footer.Children = append(footer.Children, parseRawWidget(ctx, childMap, entCtx)...) } } result = append(result, footer) @@ -432,7 +432,7 @@ func (e *Executor) parseDataViewChildren(w map[string]any, entityContext ...stri } // extractDataViewDataSource extracts the data source from a DataView widget. -func (e *Executor) extractDataViewDataSource(w map[string]any) *rawDataSource { +func extractDataViewDataSource(ctx *ExecContext, w map[string]any) *rawDataSource { ds, ok := w["DataSource"].(map[string]any) if !ok { return nil @@ -471,17 +471,17 @@ func (e *Executor) extractDataViewDataSource(w map[string]any) *rawDataSource { } // extractLabelText extracts the label text from an input widget. -func (e *Executor) extractLabelText(w map[string]any) string { +func extractLabelText(ctx *ExecContext, w map[string]any) string { labelTemplate, ok := w["LabelTemplate"].(map[string]any) if !ok { return "" } - return e.extractTextFromTemplate(labelTemplate) + return extractTextFromTemplate(ctx, labelTemplate) } // extractEditable extracts the Editable setting from an input widget. // Returns "Always", "Never", or "Conditional". -func (e *Executor) extractEditable(w map[string]any) string { +func extractEditable(ctx *ExecContext, w map[string]any) string { if editable, ok := w["Editable"].(string); ok { return editable } @@ -490,7 +490,7 @@ func (e *Executor) extractEditable(w map[string]any) string { // extractReadOnlyStyle extracts the ReadOnlyStyle from an input widget. // Returns "Inherit", "Control", or "Text". -func (e *Executor) extractReadOnlyStyle(w map[string]any) string { +func extractReadOnlyStyle(ctx *ExecContext, w map[string]any) string { if style, ok := w["ReadOnlyStyle"].(string); ok { return style } @@ -498,7 +498,7 @@ func (e *Executor) extractReadOnlyStyle(w map[string]any) string { } // extractShowLabel extracts whether the label is visible from LabelTemplate. -func (e *Executor) extractShowLabel(w map[string]any) bool { +func extractShowLabel(ctx *ExecContext, w map[string]any) bool { labelTemplate, ok := w["LabelTemplate"].(map[string]any) if !ok { return true // Default to showing label @@ -512,7 +512,7 @@ func (e *Executor) extractShowLabel(w map[string]any) bool { // extractTextFromTemplate extracts text from a ClientTemplate. // ClientTemplate structure: Template.Items[] contains Texts$Translation with Text field -func (e *Executor) extractTextFromTemplate(template map[string]any) string { +func extractTextFromTemplate(ctx *ExecContext, template map[string]any) string { // For ClientTemplate (Forms$ClientTemplate), the text is in Template.Items[].Text if innerTemplate, ok := template["Template"].(map[string]any); ok { items := getBsonArrayElements(innerTemplate["Items"]) @@ -552,7 +552,7 @@ func shortAttributeName(attr string) string { // extractAttributeRef extracts the attribute reference from an input widget. // Returns just the attribute name (last segment). -func (e *Executor) extractAttributeRef(w map[string]any) string { +func extractAttributeRef(ctx *ExecContext, w map[string]any) string { attrRef, ok := w["AttributeRef"].(map[string]any) if !ok { return "" @@ -566,10 +566,10 @@ func (e *Executor) extractAttributeRef(w map[string]any) string { // parseGalleryContent extracts the content widget from a Gallery. // entityContext is the resolved entity context from the Gallery's datasource. -func (e *Executor) parseGalleryContent(w map[string]any, entityContext ...string) []rawWidget { - ctx := "" +func parseGalleryContent(ctx *ExecContext, w map[string]any, entityContext ...string) []rawWidget { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } content := w["ContentWidget"] if content == nil { @@ -579,15 +579,15 @@ func (e *Executor) parseGalleryContent(w map[string]any, entityContext ...string if !ok { return nil } - return e.parseRawWidget(contentMap, ctx) + return parseRawWidget(ctx, contentMap, entCtx) } // parseListViewContent extracts the content widgets from a ListView. // entityContext is the resolved entity context from the enclosing list container. -func (e *Executor) parseListViewContent(w map[string]any, entityContext ...string) []rawWidget { - ctx := "" +func parseListViewContent(ctx *ExecContext, w map[string]any, entityContext ...string) []rawWidget { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } widgets := getBsonArrayElements(w["Widgets"]) if widgets == nil { @@ -599,13 +599,13 @@ func (e *Executor) parseListViewContent(w map[string]any, entityContext ...strin if !ok { continue } - result = append(result, e.parseRawWidget(wgtMap, ctx)...) + result = append(result, parseRawWidget(ctx, wgtMap, entCtx)...) } return result } // extractListViewDataSource extracts the datasource from a ListView widget. -func (e *Executor) extractListViewDataSource(w map[string]any) *rawDataSource { +func extractListViewDataSource(ctx *ExecContext, w map[string]any) *rawDataSource { ds, ok := w["DataSource"].(map[string]any) if !ok || ds == nil { return nil @@ -665,7 +665,8 @@ func (e *Executor) extractListViewDataSource(w map[string]any) *rawDataSource { } // extractSnippetRef extracts the snippet reference from a SnippetCallWidget. -func (e *Executor) extractSnippetRef(w map[string]any) string { +func extractSnippetRef(ctx *ExecContext, w map[string]any) string { + e := ctx.executor // First try the FormCall.Form path (used for BY_NAME_REFERENCE) if formCall, ok := w["FormCall"].(map[string]any); ok { if form, ok := formCall["Form"].(string); ok && form != "" { diff --git a/mdl/executor/cmd_pages_describe_pluggable.go b/mdl/executor/cmd_pages_describe_pluggable.go index 288a3784..bc557326 100644 --- a/mdl/executor/cmd_pages_describe_pluggable.go +++ b/mdl/executor/cmd_pages_describe_pluggable.go @@ -3,6 +3,7 @@ package executor import ( + "context" "strings" ) @@ -43,10 +44,10 @@ func buildPropertyTypeKeyMap(w map[string]any, withFallback bool) map[string]str // extractCustomWidgetAttribute extracts the attribute from a CustomWidget (e.g., ComboBox). // Specifically looks for attributeAssociation or attributeEnumeration properties by key, // avoiding false matches from other properties that also have AttributeRef (e.g., CaptionAttribute). -func (e *Executor) extractCustomWidgetAttribute(w map[string]any) string { +func extractCustomWidgetAttribute(ctx *ExecContext, w map[string]any) string { // Try association attribute first, then enumeration attribute for _, key := range []string{"attributeAssociation", "attributeEnumeration"} { - if attr := e.extractCustomWidgetPropertyAttributeRef(w, key); attr != "" { + if attr := extractCustomWidgetPropertyAttributeRef(ctx, w, key); attr != "" { return attr } } @@ -54,7 +55,7 @@ func (e *Executor) extractCustomWidgetAttribute(w map[string]any) string { } // extractCustomWidgetType extracts the widget type ID from a CustomWidget. -func (e *Executor) extractCustomWidgetType(w map[string]any) string { +func extractCustomWidgetType(ctx *ExecContext, w map[string]any) string { typeObj, ok := w["Type"].(map[string]any) if !ok { return "" @@ -94,9 +95,9 @@ func (e *Executor) extractCustomWidgetType(w map[string]any) string { // extractComboBoxDataSource extracts the datasource from a ComboBox CustomWidget in association mode. // Returns nil for enumeration mode (no datasource). -func (e *Executor) extractComboBoxDataSource(w map[string]any) *rawDataSource { +func extractComboBoxDataSource(ctx *ExecContext, w map[string]any) *rawDataSource { // Check if optionsSourceType is "association" first - sourceType := e.extractCustomWidgetPropertyString(w, "optionsSourceType") + sourceType := extractCustomWidgetPropertyString(ctx, w, "optionsSourceType") if sourceType != "association" { return nil } @@ -130,14 +131,14 @@ func (e *Executor) extractComboBoxDataSource(w map[string]any) *rawDataSource { continue } if ds, ok := dsVal.(map[string]any); ok && ds != nil { - return e.parseCustomWidgetDataSource(ds) + return parseCustomWidgetDataSource(ctx, ds) } } return nil } // extractDataGrid2DataSource extracts the datasource from a DataGrid2 CustomWidget. -func (e *Executor) extractDataGrid2DataSource(w map[string]any) *rawDataSource { +func extractDataGrid2DataSource(ctx *ExecContext, w map[string]any) *rawDataSource { obj, ok := w["Object"].(map[string]any) if !ok { return nil @@ -222,14 +223,14 @@ func (e *Executor) extractDataGrid2DataSource(w map[string]any) *rawDataSource { // extractDataGrid2Columns extracts the columns from a DataGrid2 CustomWidget. // entityContext is the resolved entity context from the DataGrid2's datasource. -func (e *Executor) extractDataGrid2Columns(w map[string]any, entityContext ...string) []rawDataGridColumn { +func extractDataGrid2Columns(ctx *ExecContext, w map[string]any, entityContext ...string) []rawDataGridColumn { obj, ok := w["Object"].(map[string]any) if !ok { return nil } // Build column property key map from Type.ObjectType.PropertyTypes -> columns -> ValueType.ObjectType.PropertyTypes - colPropKeyMap := e.buildColumnPropertyKeyMap(w) + colPropKeyMap := buildColumnPropertyKeyMap(ctx, w) // Search through properties for columns props := getBsonArrayElements(obj["Properties"]) @@ -248,9 +249,9 @@ func (e *Executor) extractDataGrid2Columns(w map[string]any, entityContext ...st continue } - ctx := "" + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } var columns []rawDataGridColumn for _, colObj := range objects { @@ -258,7 +259,7 @@ func (e *Executor) extractDataGrid2Columns(w map[string]any, entityContext ...st if !ok { continue } - col := e.extractDataGrid2Column(colMap, colPropKeyMap, ctx) + col := extractDataGrid2Column(ctx, colMap, colPropKeyMap, entCtx) if col.Attribute != "" || col.Caption != "" { columns = append(columns, col) } @@ -272,7 +273,7 @@ func (e *Executor) extractDataGrid2Columns(w map[string]any, entityContext ...st // buildColumnPropertyKeyMap builds a map from TypePointer ID to property key // for column-level properties (alignment, wrapText, etc.) from the widget Type. -func (e *Executor) buildColumnPropertyKeyMap(w map[string]any) map[string]string { +func buildColumnPropertyKeyMap(ctx *ExecContext, w map[string]any) map[string]string { result := make(map[string]string) widgetType, ok := w["Type"].(map[string]any) if !ok { @@ -331,7 +332,7 @@ func (e *Executor) buildColumnPropertyKeyMap(w map[string]any) map[string]string // - "dynamicText": TextTemplate for dynamic text (when showContentAs = "dynamicText") // - "alignment": enum value ("left", "center", "right") // - "wrapText": boolean ("true", "false") -func (e *Executor) extractDataGrid2Column(colObj map[string]any, colPropKeyMap map[string]string, entityContext string) rawDataGridColumn { +func extractDataGrid2Column(ctx *ExecContext, colObj map[string]any, colPropKeyMap map[string]string, entityContext string) rawDataGridColumn { col := rawDataGridColumn{} // Track if we've found the header to avoid overwriting with dynamicText's TextTemplate @@ -465,7 +466,7 @@ func (e *Executor) extractDataGrid2Column(colObj map[string]any, colPropKeyMap m if len(widgets) > 0 { for _, w := range widgets { if wMap, ok := w.(map[string]any); ok { - col.ContentWidgets = append(col.ContentWidgets, e.parseRawWidget(wMap, entityContext)...) + col.ContentWidgets = append(col.ContentWidgets, parseRawWidget(ctx, wMap, entityContext)...) } } } @@ -485,12 +486,12 @@ func (e *Executor) extractDataGrid2Column(colObj map[string]any, colPropKeyMap m if !foundHeader { // First TextTemplate with text is the header col.Caption = text - col.CaptionParams = e.extractTextTemplateParameters(textTemplate) + col.CaptionParams = extractTextTemplateParameters(ctx, textTemplate) foundHeader = true } else if col.DynamicText == "" { // Second TextTemplate is dynamicText (if showContentAs = dynamicText) col.DynamicText = text - col.DynamicTextParams = e.extractTextTemplateParameters(textTemplate) + col.DynamicTextParams = extractTextTemplateParameters(ctx, textTemplate) } break } @@ -503,12 +504,12 @@ func (e *Executor) extractDataGrid2Column(colObj map[string]any, colPropKeyMap m // extractDataGrid2ControlBar extracts the CONTROLBAR widgets from a DataGrid2 CustomWidget. // DataGrid2 stores header/filter widgets in the 'filtersPlaceholder' property, same as Gallery. -func (e *Executor) extractDataGrid2ControlBar(w map[string]any) []rawWidget { - return e.extractGalleryWidgetsByPropertyKey(w, "filtersPlaceholder") +func extractDataGrid2ControlBar(ctx *ExecContext, w map[string]any) []rawWidget { + return extractGalleryWidgetsByPropertyKey(ctx, w, "filtersPlaceholder") } // extractTextTemplateParameters extracts parameters from a TextTemplate (Forms$ClientTemplate). -func (e *Executor) extractTextTemplateParameters(textTemplate map[string]any) []string { +func extractTextTemplateParameters(ctx *ExecContext, textTemplate map[string]any) []string { params := getBsonArrayElements(textTemplate["Parameters"]) if params == nil || len(params) == 0 { return nil @@ -556,7 +557,7 @@ func (e *Executor) extractTextTemplateParameters(textTemplate map[string]any) [] // extractGalleryDataSource extracts the datasource from a Gallery widget. // Handles both Forms$Gallery and CustomWidgets$CustomWidget Gallery formats. -func (e *Executor) extractGalleryDataSource(w map[string]any) *rawDataSource { +func extractGalleryDataSource(ctx *ExecContext, w map[string]any) *rawDataSource { // First check for CustomWidget Gallery format (datasource in Object.Properties) if obj, ok := w["Object"].(map[string]any); ok { props := getBsonArrayElements(obj["Properties"]) @@ -575,7 +576,7 @@ func (e *Executor) extractGalleryDataSource(w map[string]any) *rawDataSource { continue } if ds, ok := dsVal.(map[string]any); ok && ds != nil { - result := e.parseCustomWidgetDataSource(ds) + result := parseCustomWidgetDataSource(ctx, ds) if result != nil { return result } @@ -637,7 +638,7 @@ func (e *Executor) extractGalleryDataSource(w map[string]any) *rawDataSource { } // parseCustomWidgetDataSource parses datasource from CustomWidget property format. -func (e *Executor) parseCustomWidgetDataSource(ds map[string]any) *rawDataSource { +func parseCustomWidgetDataSource(ctx *ExecContext, ds map[string]any) *rawDataSource { dsType := extractString(ds["$Type"]) switch dsType { case "CustomWidgets$CustomWidgetXPathSource": @@ -696,20 +697,20 @@ func (e *Executor) parseCustomWidgetDataSource(ds map[string]any) *rawDataSource // extractGalleryContent extracts the content widgets from a CustomWidget Gallery. // entityContext is the resolved entity context from the Gallery's datasource. -func (e *Executor) extractGalleryContent(w map[string]any, entityContext ...string) []rawWidget { - ctx := "" +func extractGalleryContent(ctx *ExecContext, w map[string]any, entityContext ...string) []rawWidget { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } - return e.extractGalleryWidgetsByPropertyKey(w, "content", ctx) + return extractGalleryWidgetsByPropertyKey(ctx, w, "content", entCtx) } // extractGalleryWidgetsByPropertyKey extracts widgets from a named property of a CustomWidget Gallery. // entityContext is the resolved entity context to propagate to child widgets. -func (e *Executor) extractGalleryWidgetsByPropertyKey(w map[string]any, targetKey string, entityContext ...string) []rawWidget { - ctx := "" +func extractGalleryWidgetsByPropertyKey(ctx *ExecContext, w map[string]any, targetKey string, entityContext ...string) []rawWidget { + entCtx := "" if len(entityContext) > 0 { - ctx = entityContext[0] + entCtx = entityContext[0] } obj, ok := w["Object"].(map[string]any) if !ok { @@ -754,7 +755,7 @@ func (e *Executor) extractGalleryWidgetsByPropertyKey(w map[string]any, targetKe if !ok { continue } - result = append(result, e.parseRawWidget(wgtMap, ctx)...) + result = append(result, parseRawWidget(ctx, wgtMap, entCtx)...) } return result } @@ -782,7 +783,7 @@ func (e *Executor) extractGalleryWidgetsByPropertyKey(w map[string]any, targetKe if !ok { continue } - result = append(result, e.parseRawWidget(wgtMap, ctx)...) + result = append(result, parseRawWidget(ctx, wgtMap, entCtx)...) } if len(result) > 0 { return result @@ -794,12 +795,12 @@ func (e *Executor) extractGalleryWidgetsByPropertyKey(w map[string]any, targetKe } // extractGalleryFilters extracts the filter widgets from a CustomWidget Gallery. -func (e *Executor) extractGalleryFilters(w map[string]any) []rawWidget { - return e.extractGalleryWidgetsByPropertyKey(w, "filtersPlaceholder") +func extractGalleryFilters(ctx *ExecContext, w map[string]any) []rawWidget { + return extractGalleryWidgetsByPropertyKey(ctx, w, "filtersPlaceholder") } // extractGallerySelection extracts the selection mode from a CustomWidget Gallery. -func (e *Executor) extractGallerySelection(w map[string]any) string { +func extractGallerySelection(ctx *ExecContext, w map[string]any) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -825,18 +826,18 @@ func (e *Executor) extractGallerySelection(w map[string]any) string { } // extractFilterAttributes extracts the filter attributes from a TextFilter/NumberFilter widget. -func (e *Executor) extractFilterAttributes(w map[string]any) []string { +func extractFilterAttributes(ctx *ExecContext, w map[string]any) []string { // Use the generic property extraction helper - return e.extractCustomWidgetPropertyAttributes(w, "attributes") + return extractCustomWidgetPropertyAttributes(ctx, w, "attributes") } // extractFilterExpression extracts the default filter expression from a TextFilter widget. -func (e *Executor) extractFilterExpression(w map[string]any) string { - return e.extractCustomWidgetPropertyString(w, "defaultFilter") +func extractFilterExpression(ctx *ExecContext, w map[string]any) string { + return extractCustomWidgetPropertyString(ctx, w, "defaultFilter") } // extractCustomWidgetPropertyAttributeRef extracts an AttributeRef value from a named CustomWidget property. -func (e *Executor) extractCustomWidgetPropertyAttributeRef(w map[string]any, propertyKey string) string { +func extractCustomWidgetPropertyAttributeRef(ctx *ExecContext, w map[string]any, propertyKey string) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -876,7 +877,7 @@ func (e *Executor) extractCustomWidgetPropertyAttributeRef(w map[string]any, pro // // This is the symmetric counterpart of extractCustomWidgetPropertyAttributeRef, // handling the EntityRef storage format instead of AttributeRef. -func (e *Executor) extractCustomWidgetPropertyAssociation(w map[string]any, propertyKey string) string { +func extractCustomWidgetPropertyAssociation(ctx *ExecContext, w map[string]any, propertyKey string) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -919,7 +920,7 @@ func (e *Executor) extractCustomWidgetPropertyAssociation(w map[string]any, prop } // extractCustomWidgetPropertyString extracts a string property value from a CustomWidget. -func (e *Executor) extractCustomWidgetPropertyString(w map[string]any, propertyKey string) string { +func extractCustomWidgetPropertyString(ctx *ExecContext, w map[string]any, propertyKey string) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -956,7 +957,7 @@ func (e *Executor) extractCustomWidgetPropertyString(w map[string]any, propertyK } // extractCustomWidgetPropertyAttributes extracts attribute references from a CustomWidget property. -func (e *Executor) extractCustomWidgetPropertyAttributes(w map[string]any, propertyKey string) []string { +func extractCustomWidgetPropertyAttributes(ctx *ExecContext, w map[string]any, propertyKey string) []string { obj, ok := w["Object"].(map[string]any) if !ok { return nil @@ -1017,7 +1018,7 @@ func (e *Executor) extractCustomWidgetPropertyAttributes(w map[string]any, prope } // extractCustomWidgetID extracts the full widget ID from a CustomWidget (e.g. "com.mendix.widget.custom.switch.Switch"). -func (e *Executor) extractCustomWidgetID(w map[string]any) string { +func extractCustomWidgetID(ctx *ExecContext, w map[string]any) string { typeObj, ok := w["Type"].(map[string]any) if !ok { return "" @@ -1041,7 +1042,7 @@ func isKnownCustomWidgetType(widgetType string) bool { // extractExplicitProperties extracts non-default property values from a CustomWidget BSON. // Returns attribute references and primitive values for properties that differ from defaults. -func (e *Executor) extractExplicitProperties(w map[string]any) []rawExplicitProp { +func extractExplicitProperties(ctx *ExecContext, w map[string]any) []rawExplicitProp { obj, ok := w["Object"].(map[string]any) if !ok { return nil @@ -1097,22 +1098,22 @@ func (e *Executor) extractExplicitProperties(w map[string]any) []rawExplicitProp } // extractImageProperties extracts properties from a pluggable Image CustomWidget. -func (e *Executor) extractImageProperties(w map[string]any, widget *rawWidget) { - widget.ImageType = e.extractCustomWidgetPropertyString(w, "datasource") - widget.ImageUrl = e.extractCustomWidgetPropertyTextTemplate(w, "imageUrl") - widget.AlternativeText = e.extractCustomWidgetPropertyTextTemplate(w, "alternativeText") - widget.ImageWidth = e.extractCustomWidgetPropertyString(w, "width") - widget.ImageHeight = e.extractCustomWidgetPropertyString(w, "height") - widget.WidthUnit = e.extractCustomWidgetPropertyString(w, "widthUnit") - widget.HeightUnit = e.extractCustomWidgetPropertyString(w, "heightUnit") - widget.DisplayAs = e.extractCustomWidgetPropertyString(w, "displayAs") - widget.Responsive = e.extractCustomWidgetPropertyString(w, "responsive") - widget.OnClickType = e.extractCustomWidgetPropertyString(w, "onClickType") - widget.Action = e.extractCustomWidgetPropertyAction(w, "onClick") +func extractImageProperties(ctx *ExecContext, w map[string]any, widget *rawWidget) { + widget.ImageType = extractCustomWidgetPropertyString(ctx, w, "datasource") + widget.ImageUrl = extractCustomWidgetPropertyTextTemplate(ctx, w, "imageUrl") + widget.AlternativeText = extractCustomWidgetPropertyTextTemplate(ctx, w, "alternativeText") + widget.ImageWidth = extractCustomWidgetPropertyString(ctx, w, "width") + widget.ImageHeight = extractCustomWidgetPropertyString(ctx, w, "height") + widget.WidthUnit = extractCustomWidgetPropertyString(ctx, w, "widthUnit") + widget.HeightUnit = extractCustomWidgetPropertyString(ctx, w, "heightUnit") + widget.DisplayAs = extractCustomWidgetPropertyString(ctx, w, "displayAs") + widget.Responsive = extractCustomWidgetPropertyString(ctx, w, "responsive") + widget.OnClickType = extractCustomWidgetPropertyString(ctx, w, "onClickType") + widget.Action = extractCustomWidgetPropertyAction(ctx, w, "onClick") } // extractCustomWidgetPropertyTextTemplate extracts text from a TextTemplate property of a CustomWidget. -func (e *Executor) extractCustomWidgetPropertyTextTemplate(w map[string]any, propertyKey string) string { +func extractCustomWidgetPropertyTextTemplate(ctx *ExecContext, w map[string]any, propertyKey string) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -1156,7 +1157,7 @@ func (e *Executor) extractCustomWidgetPropertyTextTemplate(w map[string]any, pro // extractCustomWidgetPropertyAction extracts an action description from a CustomWidget property. // Returns a formatted string like "CALL_MICROFLOW Module.Flow" or "SHOW_PAGE Module.Page". -func (e *Executor) extractCustomWidgetPropertyAction(w map[string]any, propertyKey string) string { +func extractCustomWidgetPropertyAction(ctx *ExecContext, w map[string]any, propertyKey string) string { obj, ok := w["Object"].(map[string]any) if !ok { return "" @@ -1209,3 +1210,7 @@ func (e *Executor) extractCustomWidgetPropertyAction(w map[string]any, propertyK } return "" } + +func (e *Executor) extractCustomWidgetPropertyAssociation(w map[string]any, propertyKey string) string { + return extractCustomWidgetPropertyAssociation(e.newExecContext(context.Background()), w, propertyKey) +} diff --git a/mdl/executor/cmd_pages_show.go b/mdl/executor/cmd_pages_show.go index 9befd535..4c6618dc 100644 --- a/mdl/executor/cmd_pages_show.go +++ b/mdl/executor/cmd_pages_show.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -11,7 +12,8 @@ import ( ) // showPages handles SHOW PAGES command. -func (e *Executor) showPages(moduleName string) error { +func showPages(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -74,3 +76,7 @@ func (e *Executor) showPages(moduleName string) error { } return e.writeResult(result) } + +func (e *Executor) showPages(moduleName string) error { + return showPages(e.newExecContext(context.Background()), moduleName) +} diff --git a/mdl/executor/cmd_snippets.go b/mdl/executor/cmd_snippets.go index 3eb51989..7b5bc010 100644 --- a/mdl/executor/cmd_snippets.go +++ b/mdl/executor/cmd_snippets.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -12,7 +13,8 @@ import ( ) // showSnippets handles SHOW SNIPPETS command. -func (e *Executor) showSnippets(moduleName string) error { +func showSnippets(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -60,3 +62,7 @@ func (e *Executor) showSnippets(moduleName string) error { } return e.writeResult(result) } + +func (e *Executor) showSnippets(moduleName string) error { + return showSnippets(e.newExecContext(context.Background()), moduleName) +} diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 5a11458e..755ad80b 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -95,16 +95,16 @@ func registerMicroflowHandlers(r *Registry) { func registerPageHandlers(r *Registry) { r.Register(&ast.CreatePageStmtV3{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreatePageV3(stmt.(*ast.CreatePageStmtV3)) + return execCreatePageV3(ctx, stmt.(*ast.CreatePageStmtV3)) }) r.Register(&ast.DropPageStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropPage(stmt.(*ast.DropPageStmt)) + return execDropPage(ctx, stmt.(*ast.DropPageStmt)) }) r.Register(&ast.CreateSnippetStmtV3{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateSnippetV3(stmt.(*ast.CreateSnippetStmtV3)) + return execCreateSnippetV3(ctx, stmt.(*ast.CreateSnippetStmtV3)) }) r.Register(&ast.DropSnippetStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropSnippet(stmt.(*ast.DropSnippetStmt)) + return execDropSnippet(ctx, stmt.(*ast.DropSnippetStmt)) }) r.Register(&ast.DropJavaActionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return ctx.executor.execDropJavaAction(stmt.(*ast.DropJavaActionStmt)) @@ -394,7 +394,7 @@ func registerLintHandlers(r *Registry) { func registerAlterPageHandlers(r *Registry) { r.Register(&ast.AlterPageStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterPage(stmt.(*ast.AlterPageStmt)) + return execAlterPage(ctx, stmt.(*ast.AlterPageStmt)) }) } From f3f14de00374acc88a7a84e90e8864779a81d21c Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:27:27 +0200 Subject: [PATCH 08/16] refactor: migrate security handlers to free functions --- mdl/executor/cmd_security.go | 240 +++++++++++++++++---------- mdl/executor/cmd_security_write.go | 253 +++++++++++++++++++++-------- mdl/executor/register_stubs.go | 42 ++--- 3 files changed, 354 insertions(+), 181 deletions(-) diff --git a/mdl/executor/cmd_security.go b/mdl/executor/cmd_security.go index e7977594..35794fce 100644 --- a/mdl/executor/cmd_security.go +++ b/mdl/executor/cmd_security.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -14,40 +15,42 @@ import ( ) // showProjectSecurity handles SHOW PROJECT SECURITY. -func (e *Executor) showProjectSecurity() error { +func showProjectSecurity(ctx *ExecContext) error { + e := ctx.executor ps, err := e.reader.GetProjectSecurity() if err != nil { return mdlerrors.NewBackend("read project security", err) } - fmt.Fprintf(e.output, "Security Level: %s\n", security.SecurityLevelDisplay(ps.SecurityLevel)) - fmt.Fprintf(e.output, "Check Security: %v\n", ps.CheckSecurity) - fmt.Fprintf(e.output, "Strict Mode: %v\n", ps.StrictMode) - fmt.Fprintf(e.output, "Demo Users Enabled: %v\n", ps.EnableDemoUsers) - fmt.Fprintf(e.output, "Guest Access: %v\n", ps.EnableGuestAccess) + fmt.Fprintf(ctx.Output, "Security Level: %s\n", security.SecurityLevelDisplay(ps.SecurityLevel)) + fmt.Fprintf(ctx.Output, "Check Security: %v\n", ps.CheckSecurity) + fmt.Fprintf(ctx.Output, "Strict Mode: %v\n", ps.StrictMode) + fmt.Fprintf(ctx.Output, "Demo Users Enabled: %v\n", ps.EnableDemoUsers) + fmt.Fprintf(ctx.Output, "Guest Access: %v\n", ps.EnableGuestAccess) if ps.AdminUserName != "" { - fmt.Fprintf(e.output, "Admin User: %s\n", ps.AdminUserName) + fmt.Fprintf(ctx.Output, "Admin User: %s\n", ps.AdminUserName) } if ps.GuestUserRole != "" { - fmt.Fprintf(e.output, "Guest User Role: %s\n", ps.GuestUserRole) + fmt.Fprintf(ctx.Output, "Guest User Role: %s\n", ps.GuestUserRole) } - fmt.Fprintf(e.output, "User Roles: %d\n", len(ps.UserRoles)) - fmt.Fprintf(e.output, "Demo Users: %d\n", len(ps.DemoUsers)) + fmt.Fprintf(ctx.Output, "User Roles: %d\n", len(ps.UserRoles)) + fmt.Fprintf(ctx.Output, "Demo Users: %d\n", len(ps.DemoUsers)) if ps.PasswordPolicy != nil { pp := ps.PasswordPolicy - fmt.Fprintf(e.output, "\nPassword Policy:\n") - fmt.Fprintf(e.output, " Minimum Length: %d\n", pp.MinimumLength) - fmt.Fprintf(e.output, " Require Digit: %v\n", pp.RequireDigit) - fmt.Fprintf(e.output, " Require Mixed Case: %v\n", pp.RequireMixedCase) - fmt.Fprintf(e.output, " Require Symbol: %v\n", pp.RequireSymbol) + fmt.Fprintf(ctx.Output, "\nPassword Policy:\n") + fmt.Fprintf(ctx.Output, " Minimum Length: %d\n", pp.MinimumLength) + fmt.Fprintf(ctx.Output, " Require Digit: %v\n", pp.RequireDigit) + fmt.Fprintf(ctx.Output, " Require Mixed Case: %v\n", pp.RequireMixedCase) + fmt.Fprintf(ctx.Output, " Require Symbol: %v\n", pp.RequireSymbol) } return nil } // showModuleRoles handles SHOW MODULE ROLES [IN module]. -func (e *Executor) showModuleRoles(moduleName string) error { +func showModuleRoles(ctx *ExecContext, moduleName string) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -81,7 +84,8 @@ func (e *Executor) showModuleRoles(moduleName string) error { } // showUserRoles handles SHOW USER ROLES. -func (e *Executor) showUserRoles() error { +func showUserRoles(ctx *ExecContext) error { + e := ctx.executor ps, err := e.reader.GetProjectSecurity() if err != nil { return mdlerrors.NewBackend("read project security", err) @@ -108,15 +112,16 @@ func (e *Executor) showUserRoles() error { } // showDemoUsers handles SHOW DEMO USERS. -func (e *Executor) showDemoUsers() error { +func showDemoUsers(ctx *ExecContext) error { + e := ctx.executor ps, err := e.reader.GetProjectSecurity() if err != nil { return mdlerrors.NewBackend("read project security", err) } if !ps.EnableDemoUsers { - fmt.Fprintln(e.output, "Demo users are disabled.") - fmt.Fprintln(e.output, "Enable with: ALTER PROJECT SECURITY DEMO USERS ON;") + fmt.Fprintln(ctx.Output, "Demo users are disabled.") + fmt.Fprintln(ctx.Output, "Enable with: ALTER PROJECT SECURITY DEMO USERS ON;") return nil } @@ -134,7 +139,8 @@ func (e *Executor) showDemoUsers() error { } // showAccessOnEntity handles SHOW ACCESS ON Module.Entity. -func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { +func showAccessOnEntity(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("entity name required") } @@ -161,7 +167,7 @@ func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { } if len(entity.AccessRules) == 0 { - fmt.Fprintf(e.output, "No access rules on %s\n", name) + fmt.Fprintf(ctx.Output, "No access rules on %s\n", name) return nil } @@ -171,7 +177,7 @@ func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { attrNames[string(attr.ID)] = attr.Name } - fmt.Fprintf(e.output, "Access rules for %s.%s:\n\n", name.Module, name.Name) + fmt.Fprintf(ctx.Output, "Access rules for %s.%s:\n\n", name.Module, name.Name) for i, rule := range entity.AccessRules { // Show roles @@ -184,7 +190,7 @@ func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { roleStrs = append(roleStrs, string(rid)) } } - fmt.Fprintf(e.output, "Rule %d: %s\n", i+1, strings.Join(roleStrs, ", ")) + fmt.Fprintf(ctx.Output, "Rule %d: %s\n", i+1, strings.Join(roleStrs, ", ")) // Show CRUD rights (READ/WRITE inferred from DefaultMemberAccessRights + MemberAccesses) var rights []string @@ -211,11 +217,11 @@ func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { if rule.AllowDelete { rights = append(rights, "DELETE") } - fmt.Fprintf(e.output, " Rights: %s\n", strings.Join(rights, ", ")) + fmt.Fprintf(ctx.Output, " Rights: %s\n", strings.Join(rights, ", ")) // Show default member access if rule.DefaultMemberAccessRights != "" { - fmt.Fprintf(e.output, " Default member access: %s\n", rule.DefaultMemberAccessRights) + fmt.Fprintf(ctx.Output, " Default member access: %s\n", rule.DefaultMemberAccessRights) } // Show member-level access @@ -231,21 +237,22 @@ func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { memberName = string(ma.AttributeID) } } - fmt.Fprintf(e.output, " %s: %s\n", memberName, ma.AccessRights) + fmt.Fprintf(ctx.Output, " %s: %s\n", memberName, ma.AccessRights) } // Show XPath constraint if rule.XPathConstraint != "" { - fmt.Fprintf(e.output, " WHERE '%s'\n", rule.XPathConstraint) + fmt.Fprintf(ctx.Output, " WHERE '%s'\n", rule.XPathConstraint) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } return nil } // showAccessOnMicroflow handles SHOW ACCESS ON MICROFLOW Module.MF. -func (e *Executor) showAccessOnMicroflow(name *ast.QualifiedName) error { +func showAccessOnMicroflow(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("microflow name required") } @@ -264,12 +271,12 @@ func (e *Executor) showAccessOnMicroflow(name *ast.QualifiedName) error { modName := h.GetModuleName(h.FindModuleID(mf.ContainerID)) if modName == name.Module && mf.Name == name.Name { if len(mf.AllowedModuleRoles) == 0 { - fmt.Fprintf(e.output, "No module roles granted execute access on %s.%s\n", modName, mf.Name) + fmt.Fprintf(ctx.Output, "No module roles granted execute access on %s.%s\n", modName, mf.Name) return nil } - fmt.Fprintf(e.output, "Allowed module roles for %s.%s:\n", modName, mf.Name) + fmt.Fprintf(ctx.Output, "Allowed module roles for %s.%s:\n", modName, mf.Name) for _, role := range mf.AllowedModuleRoles { - fmt.Fprintf(e.output, " %s\n", string(role)) + fmt.Fprintf(ctx.Output, " %s\n", string(role)) } return nil } @@ -279,7 +286,8 @@ func (e *Executor) showAccessOnMicroflow(name *ast.QualifiedName) error { } // showAccessOnPage handles SHOW ACCESS ON PAGE Module.Page. -func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error { +func showAccessOnPage(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("page name required") } @@ -298,12 +306,12 @@ func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error { modName := h.GetModuleName(h.FindModuleID(pg.ContainerID)) if modName == name.Module && pg.Name == name.Name { if len(pg.AllowedRoles) == 0 { - fmt.Fprintf(e.output, "No module roles granted view access on %s.%s\n", modName, pg.Name) + fmt.Fprintf(ctx.Output, "No module roles granted view access on %s.%s\n", modName, pg.Name) return nil } - fmt.Fprintf(e.output, "Allowed module roles for %s.%s:\n", modName, pg.Name) + fmt.Fprintf(ctx.Output, "Allowed module roles for %s.%s:\n", modName, pg.Name) for _, role := range pg.AllowedRoles { - fmt.Fprintf(e.output, " %s\n", string(role)) + fmt.Fprintf(ctx.Output, " %s\n", string(role)) } return nil } @@ -313,14 +321,15 @@ func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error { } // showAccessOnWorkflow handles SHOW ACCESS ON WORKFLOW Module.WF. -func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error { +func showAccessOnWorkflow(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewUnsupported("SHOW ACCESS ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting") } // showSecurityMatrix handles SHOW SECURITY MATRIX [IN module]. -func (e *Executor) showSecurityMatrix(moduleName string) error { +func showSecurityMatrix(ctx *ExecContext, moduleName string) error { + e := ctx.executor if e.format == FormatJSON { - return e.showSecurityMatrixJSON(moduleName) + return showSecurityMatrixJSON(ctx, moduleName) } h, err := e.getHierarchy() @@ -355,9 +364,9 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { if len(roles) == 0 { if moduleName != "" { - fmt.Fprintf(e.output, "No module roles found in %s\n", moduleName) + fmt.Fprintf(ctx.Output, "No module roles found in %s\n", moduleName) } else { - fmt.Fprintln(e.output, "No module roles found") + fmt.Fprintln(ctx.Output, "No module roles found") } return nil } @@ -374,16 +383,16 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { return mdlerrors.NewBackend("list domain models", err) } - fmt.Fprintf(e.output, "Security Matrix") + fmt.Fprintf(ctx.Output, "Security Matrix") if moduleName != "" { - fmt.Fprintf(e.output, " for %s", moduleName) + fmt.Fprintf(ctx.Output, " for %s", moduleName) } - fmt.Fprintln(e.output, ":") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, ":") + fmt.Fprintln(ctx.Output) // Entities section - fmt.Fprintln(e.output, "## Entity Access") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "## Entity Access") + fmt.Fprintln(ctx.Output) entityFound := false for _, dm := range dms { @@ -398,7 +407,7 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { continue } entityFound = true - fmt.Fprintf(e.output, "### %s.%s\n", modName, entity.Name) + fmt.Fprintf(ctx.Output, "### %s.%s\n", modName, entity.Name) for _, rule := range entity.AccessRules { var roleStrs []string @@ -436,19 +445,19 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { rights = append(rights, "D") } - fmt.Fprintf(e.output, " %s: %s\n", strings.Join(roleStrs, ", "), strings.Join(rights, "")) + fmt.Fprintf(ctx.Output, " %s: %s\n", strings.Join(roleStrs, ", "), strings.Join(rights, "")) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } } if !entityFound { - fmt.Fprintln(e.output, "(no entity access rules configured)") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "(no entity access rules configured)") + fmt.Fprintln(ctx.Output) } // Microflow section - fmt.Fprintln(e.output, "## Microflow Access") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "## Microflow Access") + fmt.Fprintln(ctx.Output) mfs, err := e.reader.ListMicroflows() if err != nil { @@ -470,16 +479,16 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { for _, r := range mf.AllowedModuleRoles { roleStrs = append(roleStrs, string(r)) } - fmt.Fprintf(e.output, " %s.%s: %s\n", modName, mf.Name, strings.Join(roleStrs, ", ")) + fmt.Fprintf(ctx.Output, " %s.%s: %s\n", modName, mf.Name, strings.Join(roleStrs, ", ")) } if !mfFound { - fmt.Fprintln(e.output, "(no microflow access rules configured)") + fmt.Fprintln(ctx.Output, "(no microflow access rules configured)") } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Page section - fmt.Fprintln(e.output, "## Page Access") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "## Page Access") + fmt.Fprintln(ctx.Output) pages, err := e.reader.ListPages() if err != nil { @@ -501,25 +510,26 @@ func (e *Executor) showSecurityMatrix(moduleName string) error { for _, r := range pg.AllowedRoles { roleStrs = append(roleStrs, string(r)) } - fmt.Fprintf(e.output, " %s.%s: %s\n", modName, pg.Name, strings.Join(roleStrs, ", ")) + fmt.Fprintf(ctx.Output, " %s.%s: %s\n", modName, pg.Name, strings.Join(roleStrs, ", ")) } if !pgFound { - fmt.Fprintln(e.output, "(no page access rules configured)") + fmt.Fprintln(ctx.Output, "(no page access rules configured)") } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Workflow section — workflows don't have document-level AllowedModuleRoles - fmt.Fprintln(e.output, "## Workflow Access") - fmt.Fprintln(e.output) - fmt.Fprintln(e.output, "(workflow access is controlled through triggering microflows and UserTask targeting, not document-level roles)") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "## Workflow Access") + fmt.Fprintln(ctx.Output) + fmt.Fprintln(ctx.Output, "(workflow access is controlled through triggering microflows and UserTask targeting, not document-level roles)") + fmt.Fprintln(ctx.Output) return nil } // showSecurityMatrixJSON emits the security matrix as a JSON table // with one row per access rule across entities, microflows, pages, and workflows. -func (e *Executor) showSecurityMatrixJSON(moduleName string) error { +func showSecurityMatrixJSON(ctx *ExecContext, moduleName string) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -634,7 +644,8 @@ func (e *Executor) showSecurityMatrixJSON(moduleName string) error { } // describeModuleRole handles DESCRIBE MODULE ROLE Module.RoleName. -func (e *Executor) describeModuleRole(name ast.QualifiedName) error { +func describeModuleRole(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -652,12 +663,12 @@ func (e *Executor) describeModuleRole(name ast.QualifiedName) error { } for _, mr := range ms.ModuleRoles { if mr.Name == name.Name { - fmt.Fprintf(e.output, "CREATE MODULE ROLE %s.%s", modName, mr.Name) + fmt.Fprintf(ctx.Output, "CREATE MODULE ROLE %s.%s", modName, mr.Name) if mr.Description != "" { - fmt.Fprintf(e.output, " DESCRIPTION '%s'", mr.Description) + fmt.Fprintf(ctx.Output, " DESCRIPTION '%s'", mr.Description) } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") // Show which user roles include this module role qualifiedRole := modName + "." + mr.Name @@ -672,7 +683,7 @@ func (e *Executor) describeModuleRole(name ast.QualifiedName) error { } } if len(includedBy) > 0 { - fmt.Fprintf(e.output, "\n-- Included in user roles: %s\n", strings.Join(includedBy, ", ")) + fmt.Fprintf(ctx.Output, "\n-- Included in user roles: %s\n", strings.Join(includedBy, ", ")) } } @@ -685,7 +696,8 @@ func (e *Executor) describeModuleRole(name ast.QualifiedName) error { } // describeDemoUser handles DESCRIBE DEMO USER 'name'. -func (e *Executor) describeDemoUser(userName string) error { +func describeDemoUser(ctx *ExecContext, userName string) error { + e := ctx.executor ps, err := e.reader.GetProjectSecurity() if err != nil { return mdlerrors.NewBackend("read project security", err) @@ -693,15 +705,15 @@ func (e *Executor) describeDemoUser(userName string) error { for _, du := range ps.DemoUsers { if du.UserName == userName { - fmt.Fprintf(e.output, "CREATE DEMO USER '%s' PASSWORD '***'", du.UserName) + fmt.Fprintf(ctx.Output, "CREATE DEMO USER '%s' PASSWORD '***'", du.UserName) if du.Entity != "" { - fmt.Fprintf(e.output, " ENTITY %s", du.Entity) + fmt.Fprintf(ctx.Output, " ENTITY %s", du.Entity) } if len(du.UserRoles) > 0 { - fmt.Fprintf(e.output, " (%s)", strings.Join(du.UserRoles, ", ")) + fmt.Fprintf(ctx.Output, " (%s)", strings.Join(du.UserRoles, ", ")) } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") return nil } } @@ -710,7 +722,8 @@ func (e *Executor) describeDemoUser(userName string) error { } // describeUserRole handles DESCRIBE USER ROLE Name. -func (e *Executor) describeUserRole(name ast.QualifiedName) error { +func describeUserRole(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor ps, err := e.reader.GetProjectSecurity() if err != nil { return mdlerrors.NewBackend("read project security", err) @@ -718,28 +731,28 @@ func (e *Executor) describeUserRole(name ast.QualifiedName) error { for _, ur := range ps.UserRoles { if ur.Name == name.Name { - fmt.Fprintf(e.output, "CREATE USER ROLE %s", ur.Name) + fmt.Fprintf(ctx.Output, "CREATE USER ROLE %s", ur.Name) // Module roles if len(ur.ModuleRoles) > 0 { - fmt.Fprintf(e.output, " (%s)", strings.Join(ur.ModuleRoles, ", ")) + fmt.Fprintf(ctx.Output, " (%s)", strings.Join(ur.ModuleRoles, ", ")) } if ur.ManageAllRoles { - fmt.Fprint(e.output, " MANAGE ALL ROLES") + fmt.Fprint(ctx.Output, " MANAGE ALL ROLES") } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") // Show description if present if ur.Description != "" { - fmt.Fprintf(e.output, "\n-- Description: %s\n", ur.Description) + fmt.Fprintf(ctx.Output, "\n-- Description: %s\n", ur.Description) } // Show check security flag if ur.CheckSecurity { - fmt.Fprintln(e.output, "-- Check security: enabled") + fmt.Fprintln(ctx.Output, "-- Check security: enabled") } return nil @@ -748,3 +761,54 @@ func (e *Executor) describeUserRole(name ast.QualifiedName) error { return mdlerrors.NewNotFound("user role", name.Name) } + +// Executor method wrappers — delegate to free functions for callers that +// still use the Executor receiver (e.g. executor_query.go). + +func (e *Executor) showProjectSecurity() error { + return showProjectSecurity(e.newExecContext(context.Background())) +} + +func (e *Executor) showModuleRoles(moduleName string) error { + return showModuleRoles(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) showUserRoles() error { + return showUserRoles(e.newExecContext(context.Background())) +} + +func (e *Executor) showDemoUsers() error { + return showDemoUsers(e.newExecContext(context.Background())) +} + +func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { + return showAccessOnEntity(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showAccessOnMicroflow(name *ast.QualifiedName) error { + return showAccessOnMicroflow(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error { + return showAccessOnPage(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error { + return showAccessOnWorkflow(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showSecurityMatrix(moduleName string) error { + return showSecurityMatrix(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeModuleRole(name ast.QualifiedName) error { + return describeModuleRole(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeDemoUser(userName string) error { + return describeDemoUser(e.newExecContext(context.Background()), userName) +} + +func (e *Executor) describeUserRole(name ast.QualifiedName) error { + return describeUserRole(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_security_write.go b/mdl/executor/cmd_security_write.go index 23f81b6d..27e8e260 100644 --- a/mdl/executor/cmd_security_write.go +++ b/mdl/executor/cmd_security_write.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -15,7 +16,8 @@ import ( ) // execCreateModuleRole handles CREATE MODULE ROLE Module.RoleName [DESCRIPTION '...']. -func (e *Executor) execCreateModuleRole(s *ast.CreateModuleRoleStmt) error { +func execCreateModuleRole(ctx *ExecContext, s *ast.CreateModuleRoleStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -41,14 +43,15 @@ func (e *Executor) execCreateModuleRole(s *ast.CreateModuleRoleStmt) error { return mdlerrors.NewBackend("create module role", err) } - fmt.Fprintf(e.output, "Created module role: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created module role: %s.%s\n", s.Name.Module, s.Name.Name) return nil } // execDropModuleRole handles DROP MODULE ROLE Module.RoleName. // Cascade-removes the role from all entity access rules, microflow/nanoflow/page // allowed roles, and OData service allowed roles before deleting the role itself. -func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { +func execDropModuleRole(ctx *ExecContext, s *ast.DropModuleRoleStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -83,7 +86,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { if n, err := e.writer.RemoveRoleFromAllEntities(dm.ID, qualifiedRole); err != nil { return mdlerrors.NewBackend("cascade-remove entity access rules", err) } else if n > 0 { - fmt.Fprintf(e.output, "Removed %s from %d entity access rule(s)\n", qualifiedRole, n) + fmt.Fprintf(ctx.Output, "Removed %s from %d entity access rule(s)\n", qualifiedRole, n) } } @@ -98,7 +101,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { continue } if removed, err := e.writer.RemoveFromAllowedRoles(mf.ID, qualifiedRole); err == nil && removed { - fmt.Fprintf(e.output, "Removed %s from microflow %s allowed roles\n", qualifiedRole, mf.Name) + fmt.Fprintf(ctx.Output, "Removed %s from microflow %s allowed roles\n", qualifiedRole, mf.Name) } } } @@ -111,7 +114,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { continue } if removed, err := e.writer.RemoveFromAllowedRoles(nf.ID, qualifiedRole); err == nil && removed { - fmt.Fprintf(e.output, "Removed %s from nanoflow %s allowed roles\n", qualifiedRole, nf.Name) + fmt.Fprintf(ctx.Output, "Removed %s from nanoflow %s allowed roles\n", qualifiedRole, nf.Name) } } } @@ -124,7 +127,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { continue } if removed, err := e.writer.RemoveFromAllowedRoles(pg.ID, qualifiedRole); err == nil && removed { - fmt.Fprintf(e.output, "Removed %s from page %s allowed roles\n", qualifiedRole, pg.Name) + fmt.Fprintf(ctx.Output, "Removed %s from page %s allowed roles\n", qualifiedRole, pg.Name) } } } @@ -137,7 +140,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { continue } if removed, err := e.writer.RemoveFromAllowedRoles(svc.ID, qualifiedRole); err == nil && removed { - fmt.Fprintf(e.output, "Removed %s from OData service %s allowed roles\n", qualifiedRole, svc.Name) + fmt.Fprintf(ctx.Output, "Removed %s from OData service %s allowed roles\n", qualifiedRole, svc.Name) } } } @@ -146,7 +149,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { // Cascade: remove role from user roles in ProjectSecurity if ps, err := e.reader.GetProjectSecurity(); err == nil { if n, err := e.writer.RemoveModuleRoleFromAllUserRoles(ps.ID, qualifiedRole); err == nil && n > 0 { - fmt.Fprintf(e.output, "Removed %s from %d user role(s)\n", qualifiedRole, n) + fmt.Fprintf(ctx.Output, "Removed %s from %d user role(s)\n", qualifiedRole, n) } } @@ -155,12 +158,13 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { return mdlerrors.NewBackend("drop module role", err) } - fmt.Fprintf(e.output, "Dropped module role: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped module role: %s.%s\n", s.Name.Module, s.Name.Name) return nil } // execCreateUserRole handles CREATE [OR MODIFY] USER ROLE Name (ModuleRoles) [MANAGE ALL ROLES]. -func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error { +func execCreateUserRole(ctx *ExecContext, s *ast.CreateUserRoleStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -187,7 +191,7 @@ func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error { if err := e.writer.AlterUserRoleModuleRoles(ps.ID, s.Name, true, moduleRoleNames); err != nil { return mdlerrors.NewBackend("update user role", err) } - fmt.Fprintf(e.output, "Modified user role: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Modified user role: %s\n", s.Name) return nil } } @@ -196,12 +200,13 @@ func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error { return mdlerrors.NewBackend("create user role", err) } - fmt.Fprintf(e.output, "Created user role: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created user role: %s\n", s.Name) return nil } // execAlterUserRole handles ALTER USER ROLE Name ADD/REMOVE MODULE ROLES (...). -func (e *Executor) execAlterUserRole(s *ast.AlterUserRoleStmt) error { +func execAlterUserRole(ctx *ExecContext, s *ast.AlterUserRoleStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -239,12 +244,13 @@ func (e *Executor) execAlterUserRole(s *ast.AlterUserRoleStmt) error { action = "Removed" prep = "from" } - fmt.Fprintf(e.output, "%s module roles %s %s user role %s\n", action, strings.Join(moduleRoleNames, ", "), prep, s.Name) + fmt.Fprintf(ctx.Output, "%s module roles %s %s user role %s\n", action, strings.Join(moduleRoleNames, ", "), prep, s.Name) return nil } // execDropUserRole handles DROP USER ROLE Name. -func (e *Executor) execDropUserRole(s *ast.DropUserRoleStmt) error { +func execDropUserRole(ctx *ExecContext, s *ast.DropUserRoleStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -270,12 +276,13 @@ func (e *Executor) execDropUserRole(s *ast.DropUserRoleStmt) error { return mdlerrors.NewBackend("drop user role", err) } - fmt.Fprintf(e.output, "Dropped user role: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped user role: %s\n", s.Name) return nil } // execGrantEntityAccess handles GRANT roles ON Module.Entity (rights) [WHERE '...']. -func (e *Executor) execGrantEntityAccess(s *ast.GrantEntityAccessStmt) error { +func execGrantEntityAccess(ctx *ExecContext, s *ast.GrantEntityAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -419,19 +426,20 @@ func (e *Executor) execGrantEntityAccess(s *ast.GrantEntityAccessStmt) error { if count, err := e.writer.ReconcileMemberAccesses(dm.ID, module.Name); err != nil { return mdlerrors.NewBackend("reconcile member accesses", err) } else if count > 0 && !e.quiet { - fmt.Fprintf(e.output, "Reconciled %d access rule(s) in module %s\n", count, module.Name) + fmt.Fprintf(ctx.Output, "Reconciled %d access rule(s) in module %s\n", count, module.Name) } e.trackModifiedDomainModel(module.ID, module.Name) - fmt.Fprintf(e.output, "Granted access on %s.%s to %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) + fmt.Fprintf(ctx.Output, "Granted access on %s.%s to %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) if !e.quiet { - fmt.Fprint(e.output, e.formatAccessRuleResult(s.Entity.Module, s.Entity.Name, roleNames)) + fmt.Fprint(ctx.Output, formatAccessRuleResult(ctx, s.Entity.Module, s.Entity.Name, roleNames)) } return nil } // execRevokeEntityAccess handles REVOKE roles ON Module.Entity [(rights...)]. -func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { +func execRevokeEntityAccess(ctx *ExecContext, s *ast.RevokeEntityAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -490,11 +498,11 @@ func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { } if modified == 0 { - fmt.Fprintf(e.output, "No access rules found matching %s on %s.%s\n", strings.Join(roleNames, ", "), s.Entity.Module, s.Entity.Name) + fmt.Fprintf(ctx.Output, "No access rules found matching %s on %s.%s\n", strings.Join(roleNames, ", "), s.Entity.Module, s.Entity.Name) } else { - fmt.Fprintf(e.output, "Revoked partial access on %s.%s from %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) + fmt.Fprintf(ctx.Output, "Revoked partial access on %s.%s from %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) if !e.quiet { - fmt.Fprint(e.output, e.formatAccessRuleResult(s.Entity.Module, s.Entity.Name, roleNames)) + fmt.Fprint(ctx.Output, formatAccessRuleResult(ctx, s.Entity.Module, s.Entity.Name, roleNames)) } } } else { @@ -505,11 +513,11 @@ func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { } if modified == 0 { - fmt.Fprintf(e.output, "No access rules found matching %s on %s.%s\n", strings.Join(roleNames, ", "), s.Entity.Module, s.Entity.Name) + fmt.Fprintf(ctx.Output, "No access rules found matching %s on %s.%s\n", strings.Join(roleNames, ", "), s.Entity.Module, s.Entity.Name) } else { - fmt.Fprintf(e.output, "Revoked access on %s.%s from %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) + fmt.Fprintf(ctx.Output, "Revoked access on %s.%s from %s\n", s.Entity.Module, s.Entity.Name, strings.Join(roleNames, ", ")) if !e.quiet { - fmt.Fprint(e.output, " Result: (no access)\n") + fmt.Fprint(ctx.Output, " Result: (no access)\n") } } } @@ -518,7 +526,8 @@ func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { } // execGrantMicroflowAccess handles GRANT EXECUTE ON MICROFLOW Module.MF TO roles. -func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) error { +func execGrantMicroflowAccess(ctx *ExecContext, s *ast.GrantMicroflowAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -543,7 +552,7 @@ func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) err // Validate all roles exist for _, role := range s.Roles { - if err := e.validateModuleRole(role); err != nil { + if err := validateModuleRole(ctx, role); err != nil { return err } } @@ -569,9 +578,9 @@ func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) err } if len(added) == 0 { - fmt.Fprintf(e.output, "All specified roles already have execute access on %s.%s\n", modName, mf.Name) + fmt.Fprintf(ctx.Output, "All specified roles already have execute access on %s.%s\n", modName, mf.Name) } else { - fmt.Fprintf(e.output, "Granted execute access on %s.%s to %s\n", modName, mf.Name, strings.Join(added, ", ")) + fmt.Fprintf(ctx.Output, "Granted execute access on %s.%s to %s\n", modName, mf.Name, strings.Join(added, ", ")) } return nil } @@ -580,7 +589,8 @@ func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) err } // execRevokeMicroflowAccess handles REVOKE EXECUTE ON MICROFLOW Module.MF FROM roles. -func (e *Executor) execRevokeMicroflowAccess(s *ast.RevokeMicroflowAccessStmt) error { +func execRevokeMicroflowAccess(ctx *ExecContext, s *ast.RevokeMicroflowAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -625,9 +635,9 @@ func (e *Executor) execRevokeMicroflowAccess(s *ast.RevokeMicroflowAccessStmt) e } if len(removed) == 0 { - fmt.Fprintf(e.output, "None of the specified roles had execute access on %s.%s\n", modName, mf.Name) + fmt.Fprintf(ctx.Output, "None of the specified roles had execute access on %s.%s\n", modName, mf.Name) } else { - fmt.Fprintf(e.output, "Revoked execute access on %s.%s from %s\n", modName, mf.Name, strings.Join(removed, ", ")) + fmt.Fprintf(ctx.Output, "Revoked execute access on %s.%s from %s\n", modName, mf.Name, strings.Join(removed, ", ")) } return nil } @@ -636,7 +646,8 @@ func (e *Executor) execRevokeMicroflowAccess(s *ast.RevokeMicroflowAccessStmt) e } // execGrantPageAccess handles GRANT VIEW ON PAGE Module.Page TO roles. -func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { +func execGrantPageAccess(ctx *ExecContext, s *ast.GrantPageAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -661,7 +672,7 @@ func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { // Validate all roles exist for _, role := range s.Roles { - if err := e.validateModuleRole(role); err != nil { + if err := validateModuleRole(ctx, role); err != nil { return err } } @@ -687,9 +698,9 @@ func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { } if len(added) == 0 { - fmt.Fprintf(e.output, "All specified roles already have view access on %s.%s\n", modName, pg.Name) + fmt.Fprintf(ctx.Output, "All specified roles already have view access on %s.%s\n", modName, pg.Name) } else { - fmt.Fprintf(e.output, "Granted view access on %s.%s to %s\n", modName, pg.Name, strings.Join(added, ", ")) + fmt.Fprintf(ctx.Output, "Granted view access on %s.%s to %s\n", modName, pg.Name, strings.Join(added, ", ")) } return nil } @@ -698,7 +709,8 @@ func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { } // execRevokePageAccess handles REVOKE VIEW ON PAGE Module.Page FROM roles. -func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error { +func execRevokePageAccess(ctx *ExecContext, s *ast.RevokePageAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -743,9 +755,9 @@ func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error { } if len(removed) == 0 { - fmt.Fprintf(e.output, "None of the specified roles had view access on %s.%s\n", modName, pg.Name) + fmt.Fprintf(ctx.Output, "None of the specified roles had view access on %s.%s\n", modName, pg.Name) } else { - fmt.Fprintf(e.output, "Revoked view access on %s.%s from %s\n", modName, pg.Name, strings.Join(removed, ", ")) + fmt.Fprintf(ctx.Output, "Revoked view access on %s.%s from %s\n", modName, pg.Name, strings.Join(removed, ", ")) } return nil } @@ -756,19 +768,20 @@ func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error { // execGrantWorkflowAccess handles GRANT EXECUTE ON WORKFLOW Module.WF TO roles. // Mendix workflows do not have a document-level AllowedModuleRoles field (unlike // microflows and pages), so this operation is not supported. -func (e *Executor) execGrantWorkflowAccess(s *ast.GrantWorkflowAccessStmt) error { +func execGrantWorkflowAccess(ctx *ExecContext, s *ast.GrantWorkflowAccessStmt) error { return mdlerrors.NewUnsupported("GRANT EXECUTE ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting") } // execRevokeWorkflowAccess handles REVOKE EXECUTE ON WORKFLOW Module.WF FROM roles. // Mendix workflows do not have a document-level AllowedModuleRoles field (unlike // microflows and pages), so this operation is not supported. -func (e *Executor) execRevokeWorkflowAccess(s *ast.RevokeWorkflowAccessStmt) error { +func execRevokeWorkflowAccess(ctx *ExecContext, s *ast.RevokeWorkflowAccessStmt) error { return mdlerrors.NewUnsupported("REVOKE EXECUTE ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting") } // validateModuleRole checks that a module role exists in the project. -func (e *Executor) validateModuleRole(role ast.QualifiedName) error { +func validateModuleRole(ctx *ExecContext, role ast.QualifiedName) error { + e := ctx.executor module, err := e.findModule(role.Module) if err != nil { return fmt.Errorf("module not found for role %s.%s: %w", role.Module, role.Name, err) @@ -789,7 +802,8 @@ func (e *Executor) validateModuleRole(role ast.QualifiedName) error { } // execAlterProjectSecurity handles ALTER PROJECT SECURITY LEVEL/DEMO USERS. -func (e *Executor) execAlterProjectSecurity(s *ast.AlterProjectSecurityStmt) error { +func execAlterProjectSecurity(ctx *ExecContext, s *ast.AlterProjectSecurityStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -816,7 +830,7 @@ func (e *Executor) execAlterProjectSecurity(s *ast.AlterProjectSecurityStmt) err if err := e.writer.SetProjectSecurityLevel(ps.ID, bsonLevel); err != nil { return mdlerrors.NewBackend("set security level", err) } - fmt.Fprintf(e.output, "Set project security level to %s\n", s.SecurityLevel) + fmt.Fprintf(ctx.Output, "Set project security level to %s\n", s.SecurityLevel) } if s.DemoUsersEnabled != nil { @@ -827,14 +841,15 @@ func (e *Executor) execAlterProjectSecurity(s *ast.AlterProjectSecurityStmt) err if *s.DemoUsersEnabled { state = "enabled" } - fmt.Fprintf(e.output, "Demo users %s\n", state) + fmt.Fprintf(ctx.Output, "Demo users %s\n", state) } return nil } // execCreateDemoUser handles CREATE [OR MODIFY] DEMO USER 'name' PASSWORD 'pw' [ENTITY Module.Entity] (Roles). -func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { +func execCreateDemoUser(ctx *ExecContext, s *ast.CreateDemoUserStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -876,7 +891,7 @@ func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { if err := e.writer.AddDemoUser(ps.ID, s.UserName, s.Password, entity, mergedRoles); err != nil { return mdlerrors.NewBackend("update demo user", err) } - fmt.Fprintf(e.output, "Modified demo user: %s\n", s.UserName) + fmt.Fprintf(ctx.Output, "Modified demo user: %s\n", s.UserName) return nil } } @@ -884,7 +899,7 @@ func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { // Resolve entity: use explicit value or auto-detect from domain models entity := s.Entity if entity == "" { - detected, err := e.detectUserEntity() + detected, err := detectUserEntity(ctx) if err != nil { return err } @@ -895,12 +910,13 @@ func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { return mdlerrors.NewBackend("create demo user", err) } - fmt.Fprintf(e.output, "Created demo user: %s (entity: %s)\n", s.UserName, entity) + fmt.Fprintf(ctx.Output, "Created demo user: %s (entity: %s)\n", s.UserName, entity) return nil } // detectUserEntity finds the entity that generalizes System.User. -func (e *Executor) detectUserEntity() (string, error) { +func detectUserEntity(ctx *ExecContext) (string, error) { + e := ctx.executor modules, err := e.reader.ListModules() if err != nil { return "", mdlerrors.NewBackend("list modules", err) @@ -944,7 +960,8 @@ func joinCandidates(candidates []string) string { } // execDropDemoUser handles DROP DEMO USER 'name'. -func (e *Executor) execDropDemoUser(s *ast.DropDemoUserStmt) error { +func execDropDemoUser(ctx *ExecContext, s *ast.DropDemoUserStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -970,7 +987,7 @@ func (e *Executor) execDropDemoUser(s *ast.DropDemoUserStmt) error { return mdlerrors.NewBackend("drop demo user", err) } - fmt.Fprintf(e.output, "Dropped demo user: %s\n", s.UserName) + fmt.Fprintf(ctx.Output, "Dropped demo user: %s\n", s.UserName) return nil } @@ -979,7 +996,8 @@ func (e *Executor) execDropDemoUser(s *ast.DropDemoUserStmt) error { // ============================================================================ // execGrantODataServiceAccess handles GRANT ACCESS ON ODATA SERVICE Module.Svc TO roles. -func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStmt) error { +func execGrantODataServiceAccess(ctx *ExecContext, s *ast.GrantODataServiceAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1004,7 +1022,7 @@ func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStm // Validate all roles exist for _, role := range s.Roles { - if err := e.validateModuleRole(role); err != nil { + if err := validateModuleRole(ctx, role); err != nil { return err } } @@ -1030,9 +1048,9 @@ func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStm } if len(added) == 0 { - fmt.Fprintf(e.output, "All specified roles already have access on OData service %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "All specified roles already have access on OData service %s.%s\n", modName, svc.Name) } else { - fmt.Fprintf(e.output, "Granted access on OData service %s.%s to %s\n", modName, svc.Name, strings.Join(added, ", ")) + fmt.Fprintf(ctx.Output, "Granted access on OData service %s.%s to %s\n", modName, svc.Name, strings.Join(added, ", ")) } return nil } @@ -1041,7 +1059,8 @@ func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStm } // execRevokeODataServiceAccess handles REVOKE ACCESS ON ODATA SERVICE Module.Svc FROM roles. -func (e *Executor) execRevokeODataServiceAccess(s *ast.RevokeODataServiceAccessStmt) error { +func execRevokeODataServiceAccess(ctx *ExecContext, s *ast.RevokeODataServiceAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1086,9 +1105,9 @@ func (e *Executor) execRevokeODataServiceAccess(s *ast.RevokeODataServiceAccessS } if len(removed) == 0 { - fmt.Fprintf(e.output, "None of the specified roles had access on OData service %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "None of the specified roles had access on OData service %s.%s\n", modName, svc.Name) } else { - fmt.Fprintf(e.output, "Revoked access on OData service %s.%s from %s\n", modName, svc.Name, strings.Join(removed, ", ")) + fmt.Fprintf(ctx.Output, "Revoked access on OData service %s.%s from %s\n", modName, svc.Name, strings.Join(removed, ", ")) } return nil } @@ -1101,7 +1120,8 @@ func (e *Executor) execRevokeODataServiceAccess(s *ast.RevokeODataServiceAccessS // ============================================================================ // execGrantPublishedRestServiceAccess handles GRANT ACCESS ON PUBLISHED REST SERVICE Module.Svc TO roles. -func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRestServiceAccessStmt) error { +func execGrantPublishedRestServiceAccess(ctx *ExecContext, s *ast.GrantPublishedRestServiceAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1131,7 +1151,7 @@ func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRest // Validate all roles exist for _, role := range s.Roles { - if err := e.validateModuleRole(role); err != nil { + if err := validateModuleRole(ctx, role); err != nil { return err } } @@ -1157,9 +1177,9 @@ func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRest } if len(added) == 0 { - fmt.Fprintf(e.output, "All specified roles already have access on published REST service %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "All specified roles already have access on published REST service %s.%s\n", modName, svc.Name) } else { - fmt.Fprintf(e.output, "Granted access on published REST service %s.%s to %s\n", modName, svc.Name, strings.Join(added, ", ")) + fmt.Fprintf(ctx.Output, "Granted access on published REST service %s.%s to %s\n", modName, svc.Name, strings.Join(added, ", ")) } return nil } @@ -1168,7 +1188,8 @@ func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRest } // execRevokePublishedRestServiceAccess handles REVOKE ACCESS ON PUBLISHED REST SERVICE Module.Svc FROM roles. -func (e *Executor) execRevokePublishedRestServiceAccess(s *ast.RevokePublishedRestServiceAccessStmt) error { +func execRevokePublishedRestServiceAccess(ctx *ExecContext, s *ast.RevokePublishedRestServiceAccessStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1212,9 +1233,9 @@ func (e *Executor) execRevokePublishedRestServiceAccess(s *ast.RevokePublishedRe } if len(removed) == 0 { - fmt.Fprintf(e.output, "None of the specified roles had access on published REST service %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "None of the specified roles had access on published REST service %s.%s\n", modName, svc.Name) } else { - fmt.Fprintf(e.output, "Revoked access on published REST service %s.%s from %s\n", modName, svc.Name, strings.Join(removed, ", ")) + fmt.Fprintf(ctx.Output, "Revoked access on published REST service %s.%s from %s\n", modName, svc.Name, strings.Join(removed, ", ")) } return nil } @@ -1223,7 +1244,8 @@ func (e *Executor) execRevokePublishedRestServiceAccess(s *ast.RevokePublishedRe } // execUpdateSecurity handles UPDATE SECURITY [IN Module]. -func (e *Executor) execUpdateSecurity(s *ast.UpdateSecurityStmt) error { +func execUpdateSecurity(ctx *ExecContext, s *ast.UpdateSecurityStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1249,14 +1271,101 @@ func (e *Executor) execUpdateSecurity(s *ast.UpdateSecurityStmt) error { return mdlerrors.NewBackend(fmt.Sprintf("reconcile security for module %s", mod.Name), err) } if count > 0 { - fmt.Fprintf(e.output, "Reconciled %d access rule(s) in module %s\n", count, mod.Name) + fmt.Fprintf(ctx.Output, "Reconciled %d access rule(s) in module %s\n", count, mod.Name) totalModified += count } } if totalModified == 0 { - fmt.Fprintf(e.output, "All entity access rules are up to date\n") + fmt.Fprintf(ctx.Output, "All entity access rules are up to date\n") } return nil } + +// Executor method wrappers — delegate to free functions for callers that +// still use the Executor receiver (e.g. executor_query.go). + +func (e *Executor) execCreateModuleRole(s *ast.CreateModuleRoleStmt) error { + return execCreateModuleRole(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { + return execDropModuleRole(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error { + return execCreateUserRole(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execAlterUserRole(s *ast.AlterUserRoleStmt) error { + return execAlterUserRole(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execDropUserRole(s *ast.DropUserRoleStmt) error { + return execDropUserRole(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantEntityAccess(s *ast.GrantEntityAccessStmt) error { + return execGrantEntityAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { + return execRevokeEntityAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) error { + return execGrantMicroflowAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokeMicroflowAccess(s *ast.RevokeMicroflowAccessStmt) error { + return execRevokeMicroflowAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { + return execGrantPageAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error { + return execRevokePageAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantWorkflowAccess(s *ast.GrantWorkflowAccessStmt) error { + return execGrantWorkflowAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokeWorkflowAccess(s *ast.RevokeWorkflowAccessStmt) error { + return execRevokeWorkflowAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execAlterProjectSecurity(s *ast.AlterProjectSecurityStmt) error { + return execAlterProjectSecurity(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { + return execCreateDemoUser(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execDropDemoUser(s *ast.DropDemoUserStmt) error { + return execDropDemoUser(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStmt) error { + return execGrantODataServiceAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokeODataServiceAccess(s *ast.RevokeODataServiceAccessStmt) error { + return execRevokeODataServiceAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRestServiceAccessStmt) error { + return execGrantPublishedRestServiceAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execRevokePublishedRestServiceAccess(s *ast.RevokePublishedRestServiceAccessStmt) error { + return execRevokePublishedRestServiceAccess(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execUpdateSecurity(s *ast.UpdateSecurityStmt) error { + return execUpdateSecurity(e.newExecContext(context.Background()), s) +} diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 755ad80b..b12ef9f8 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -128,55 +128,55 @@ func registerPageHandlers(r *Registry) { func registerSecurityHandlers(r *Registry) { r.Register(&ast.CreateModuleRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateModuleRole(stmt.(*ast.CreateModuleRoleStmt)) + return execCreateModuleRole(ctx, stmt.(*ast.CreateModuleRoleStmt)) }) r.Register(&ast.DropModuleRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropModuleRole(stmt.(*ast.DropModuleRoleStmt)) + return execDropModuleRole(ctx, stmt.(*ast.DropModuleRoleStmt)) }) r.Register(&ast.CreateUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateUserRole(stmt.(*ast.CreateUserRoleStmt)) + return execCreateUserRole(ctx, stmt.(*ast.CreateUserRoleStmt)) }) r.Register(&ast.AlterUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterUserRole(stmt.(*ast.AlterUserRoleStmt)) + return execAlterUserRole(ctx, stmt.(*ast.AlterUserRoleStmt)) }) r.Register(&ast.DropUserRoleStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropUserRole(stmt.(*ast.DropUserRoleStmt)) + return execDropUserRole(ctx, stmt.(*ast.DropUserRoleStmt)) }) r.Register(&ast.GrantEntityAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantEntityAccess(stmt.(*ast.GrantEntityAccessStmt)) + return execGrantEntityAccess(ctx, stmt.(*ast.GrantEntityAccessStmt)) }) r.Register(&ast.RevokeEntityAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokeEntityAccess(stmt.(*ast.RevokeEntityAccessStmt)) + return execRevokeEntityAccess(ctx, stmt.(*ast.RevokeEntityAccessStmt)) }) r.Register(&ast.GrantMicroflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantMicroflowAccess(stmt.(*ast.GrantMicroflowAccessStmt)) + return execGrantMicroflowAccess(ctx, stmt.(*ast.GrantMicroflowAccessStmt)) }) r.Register(&ast.RevokeMicroflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokeMicroflowAccess(stmt.(*ast.RevokeMicroflowAccessStmt)) + return execRevokeMicroflowAccess(ctx, stmt.(*ast.RevokeMicroflowAccessStmt)) }) r.Register(&ast.GrantPageAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantPageAccess(stmt.(*ast.GrantPageAccessStmt)) + return execGrantPageAccess(ctx, stmt.(*ast.GrantPageAccessStmt)) }) r.Register(&ast.RevokePageAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokePageAccess(stmt.(*ast.RevokePageAccessStmt)) + return execRevokePageAccess(ctx, stmt.(*ast.RevokePageAccessStmt)) }) r.Register(&ast.GrantWorkflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantWorkflowAccess(stmt.(*ast.GrantWorkflowAccessStmt)) + return execGrantWorkflowAccess(ctx, stmt.(*ast.GrantWorkflowAccessStmt)) }) r.Register(&ast.RevokeWorkflowAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokeWorkflowAccess(stmt.(*ast.RevokeWorkflowAccessStmt)) + return execRevokeWorkflowAccess(ctx, stmt.(*ast.RevokeWorkflowAccessStmt)) }) r.Register(&ast.AlterProjectSecurityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterProjectSecurity(stmt.(*ast.AlterProjectSecurityStmt)) + return execAlterProjectSecurity(ctx, stmt.(*ast.AlterProjectSecurityStmt)) }) r.Register(&ast.CreateDemoUserStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateDemoUser(stmt.(*ast.CreateDemoUserStmt)) + return execCreateDemoUser(ctx, stmt.(*ast.CreateDemoUserStmt)) }) r.Register(&ast.DropDemoUserStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropDemoUser(stmt.(*ast.DropDemoUserStmt)) + return execDropDemoUser(ctx, stmt.(*ast.DropDemoUserStmt)) }) r.Register(&ast.UpdateSecurityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execUpdateSecurity(stmt.(*ast.UpdateSecurityStmt)) + return execUpdateSecurity(ctx, stmt.(*ast.UpdateSecurityStmt)) }) } @@ -296,16 +296,16 @@ func registerRESTHandlers(r *Registry) { return ctx.executor.createExternalEntities(stmt.(*ast.CreateExternalEntitiesStmt)) }) r.Register(&ast.GrantODataServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantODataServiceAccess(stmt.(*ast.GrantODataServiceAccessStmt)) + return execGrantODataServiceAccess(ctx, stmt.(*ast.GrantODataServiceAccessStmt)) }) r.Register(&ast.RevokeODataServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokeODataServiceAccess(stmt.(*ast.RevokeODataServiceAccessStmt)) + return execRevokeODataServiceAccess(ctx, stmt.(*ast.RevokeODataServiceAccessStmt)) }) r.Register(&ast.GrantPublishedRestServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execGrantPublishedRestServiceAccess(stmt.(*ast.GrantPublishedRestServiceAccessStmt)) + return execGrantPublishedRestServiceAccess(ctx, stmt.(*ast.GrantPublishedRestServiceAccessStmt)) }) r.Register(&ast.RevokePublishedRestServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRevokePublishedRestServiceAccess(stmt.(*ast.RevokePublishedRestServiceAccessStmt)) + return execRevokePublishedRestServiceAccess(ctx, stmt.(*ast.RevokePublishedRestServiceAccessStmt)) }) } From 7e27f67204ffd00b63f310b82600cb4267fc2ced Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:38:51 +0200 Subject: [PATCH 09/16] refactor: migrate navigation, settings, image, and workflow handlers to free functions --- mdl/executor/cmd_alter_workflow.go | 51 +++++++------ mdl/executor/cmd_imagecollections.go | 59 ++++++++++----- mdl/executor/cmd_navigation.go | 107 +++++++++++++++++---------- mdl/executor/cmd_settings.go | 73 +++++++++++++----- mdl/executor/cmd_workflows.go | 28 +++++-- mdl/executor/cmd_workflows_write.go | 49 +++++++----- mdl/executor/register_stubs.go | 52 ++++++------- 7 files changed, 273 insertions(+), 146 deletions(-) diff --git a/mdl/executor/cmd_alter_workflow.go b/mdl/executor/cmd_alter_workflow.go index 2c4ddd95..97eee0dd 100644 --- a/mdl/executor/cmd_alter_workflow.go +++ b/mdl/executor/cmd_alter_workflow.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -20,7 +21,8 @@ import ( const bsonArrayMarker = int32(3) // execAlterWorkflow handles ALTER WORKFLOW Module.Name { operations }. -func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { +func execAlterWorkflow(ctx *ExecContext, s *ast.AlterWorkflowStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -81,7 +83,7 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("SET ACTIVITY", err) } case *ast.InsertAfterOp: - if err := e.applyInsertAfterActivity(rawData, o); err != nil { + if err := applyInsertAfterActivity(e, rawData, o); err != nil { return mdlerrors.NewBackend("INSERT AFTER", err) } case *ast.DropActivityOp: @@ -89,11 +91,11 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("DROP ACTIVITY", err) } case *ast.ReplaceActivityOp: - if err := e.applyReplaceActivity(rawData, o); err != nil { + if err := applyReplaceActivity(e, rawData, o); err != nil { return mdlerrors.NewBackend("REPLACE ACTIVITY", err) } case *ast.InsertOutcomeOp: - if err := e.applyInsertOutcome(rawData, o); err != nil { + if err := applyInsertOutcome(e, rawData, o); err != nil { return mdlerrors.NewBackend("INSERT OUTCOME", err) } case *ast.DropOutcomeOp: @@ -101,7 +103,7 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("DROP OUTCOME", err) } case *ast.InsertPathOp: - if err := e.applyInsertPath(rawData, o); err != nil { + if err := applyInsertPath(e, rawData, o); err != nil { return mdlerrors.NewBackend("INSERT PATH", err) } case *ast.DropPathOp: @@ -109,7 +111,7 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("DROP PATH", err) } case *ast.InsertBranchOp: - if err := e.applyInsertBranch(rawData, o); err != nil { + if err := applyInsertBranch(e, rawData, o); err != nil { return mdlerrors.NewBackend("INSERT BRANCH", err) } case *ast.DropBranchOp: @@ -117,7 +119,7 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("DROP BRANCH", err) } case *ast.InsertBoundaryEventOp: - if err := e.applyInsertBoundaryEvent(rawData, o); err != nil { + if err := applyInsertBoundaryEvent(e, rawData, o); err != nil { return mdlerrors.NewBackend("INSERT BOUNDARY EVENT", err) } case *ast.DropBoundaryEventOp: @@ -141,10 +143,15 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered workflow %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Altered workflow %s.%s\n", s.Name.Module, s.Name.Name) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { + return execAlterWorkflow(e.newExecContext(context.Background()), s) +} + // applySetWorkflowProperty sets a workflow-level property in raw BSON. func applySetWorkflowProperty(doc *bson.D, op *ast.SetWorkflowPropertyOp) error { switch op.Property { @@ -483,9 +490,9 @@ func deduplicateNewActivityName(act workflows.WorkflowActivity, existingNames ma // buildSubFlowBson builds a Workflows$Flow BSON document from AST activity nodes, // with auto-binding and name deduplication against existing workflow activities. -func (e *Executor) buildSubFlowBson(doc bson.D, activities []ast.WorkflowActivityNode) bson.D { +func buildSubFlowBson(e *Executor, doc bson.D, activities []ast.WorkflowActivityNode) bson.D { subActs := buildWorkflowActivities(activities) - e.autoBindActivitiesInFlow(subActs) + autoBindActivitiesInFlow(e, subActs) existingNames := collectAllActivityNames(doc) for _, act := range subActs { deduplicateNewActivityName(act, existingNames) @@ -506,7 +513,7 @@ func (e *Executor) buildSubFlowBson(doc bson.D, activities []ast.WorkflowActivit } // applyInsertAfterActivity inserts a new activity after a named activity. -func (e *Executor) applyInsertAfterActivity(doc bson.D, op *ast.InsertAfterOp) error { +func applyInsertAfterActivity(e *Executor, doc bson.D, op *ast.InsertAfterOp) error { idx, activities, containingFlow, err := findActivityIndex(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -518,7 +525,7 @@ func (e *Executor) applyInsertAfterActivity(doc bson.D, op *ast.InsertAfterOp) e } // Auto-bind parameters and deduplicate against existing workflow names - e.autoBindActivitiesInFlow(newActs) + autoBindActivitiesInFlow(e, newActs) existingNames := collectAllActivityNames(doc) for _, act := range newActs { deduplicateNewActivityName(act, existingNames) @@ -558,7 +565,7 @@ func applyDropActivity(doc bson.D, op *ast.DropActivityOp) error { } // applyReplaceActivity replaces an activity in place. -func (e *Executor) applyReplaceActivity(doc bson.D, op *ast.ReplaceActivityOp) error { +func applyReplaceActivity(e *Executor, doc bson.D, op *ast.ReplaceActivityOp) error { idx, activities, containingFlow, err := findActivityIndex(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -569,7 +576,7 @@ func (e *Executor) applyReplaceActivity(doc bson.D, op *ast.ReplaceActivityOp) e return mdlerrors.NewValidation("failed to build replacement activity") } - e.autoBindActivitiesInFlow(newActs) + autoBindActivitiesInFlow(e, newActs) existingNames := collectAllActivityNames(doc) for _, act := range newActs { deduplicateNewActivityName(act, existingNames) @@ -593,7 +600,7 @@ func (e *Executor) applyReplaceActivity(doc bson.D, op *ast.ReplaceActivityOp) e } // applyInsertOutcome adds a new outcome to a user task. -func (e *Executor) applyInsertOutcome(doc bson.D, op *ast.InsertOutcomeOp) error { +func applyInsertOutcome(e *Executor, doc bson.D, op *ast.InsertOutcomeOp) error { actDoc, err := findActivityByCaption(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -607,7 +614,7 @@ func (e *Executor) applyInsertOutcome(doc bson.D, op *ast.InsertOutcomeOp) error // Build sub-flow if activities provided if len(op.Activities) > 0 { - outcomeDoc = append(outcomeDoc, bson.E{Key: "Flow", Value: e.buildSubFlowBson(doc, op.Activities)}) + outcomeDoc = append(outcomeDoc, bson.E{Key: "Flow", Value: buildSubFlowBson(e, doc, op.Activities)}) } outcomeDoc = append(outcomeDoc, @@ -659,7 +666,7 @@ func applyDropOutcome(doc bson.D, op *ast.DropOutcomeOp) error { } // applyInsertPath adds a new path to a parallel split. -func (e *Executor) applyInsertPath(doc bson.D, op *ast.InsertPathOp) error { +func applyInsertPath(e *Executor, doc bson.D, op *ast.InsertPathOp) error { actDoc, err := findActivityByCaption(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -671,7 +678,7 @@ func (e *Executor) applyInsertPath(doc bson.D, op *ast.InsertPathOp) error { } if len(op.Activities) > 0 { - pathDoc = append(pathDoc, bson.E{Key: "Flow", Value: e.buildSubFlowBson(doc, op.Activities)}) + pathDoc = append(pathDoc, bson.E{Key: "Flow", Value: buildSubFlowBson(e, doc, op.Activities)}) } pathDoc = append(pathDoc, bson.E{Key: "PersistentId", Value: mpr.IDToBsonBinary(mpr.GenerateID())}) @@ -718,7 +725,7 @@ func applyDropPath(doc bson.D, op *ast.DropPathOp) error { } // applyInsertBranch adds a new branch to a decision. -func (e *Executor) applyInsertBranch(doc bson.D, op *ast.InsertBranchOp) error { +func applyInsertBranch(e *Executor, doc bson.D, op *ast.InsertBranchOp) error { actDoc, err := findActivityByCaption(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -753,7 +760,7 @@ func (e *Executor) applyInsertBranch(doc bson.D, op *ast.InsertBranchOp) error { } if len(op.Activities) > 0 { - outcomeDoc = append(outcomeDoc, bson.E{Key: "Flow", Value: e.buildSubFlowBson(doc, op.Activities)}) + outcomeDoc = append(outcomeDoc, bson.E{Key: "Flow", Value: buildSubFlowBson(e, doc, op.Activities)}) } outcomes := dGetArrayElements(dGet(actDoc, "Outcomes")) @@ -819,7 +826,7 @@ func applyDropBranch(doc bson.D, op *ast.DropBranchOp) error { } // applyInsertBoundaryEvent adds a boundary event to an activity. -func (e *Executor) applyInsertBoundaryEvent(doc bson.D, op *ast.InsertBoundaryEventOp) error { +func applyInsertBoundaryEvent(e *Executor, doc bson.D, op *ast.InsertBoundaryEventOp) error { actDoc, err := findActivityByCaption(doc, op.ActivityRef, op.AtPosition) if err != nil { return err @@ -844,7 +851,7 @@ func (e *Executor) applyInsertBoundaryEvent(doc bson.D, op *ast.InsertBoundaryEv } if len(op.Activities) > 0 { - eventDoc = append(eventDoc, bson.E{Key: "Flow", Value: e.buildSubFlowBson(doc, op.Activities)}) + eventDoc = append(eventDoc, bson.E{Key: "Flow", Value: buildSubFlowBson(e, doc, op.Activities)}) } eventDoc = append(eventDoc, bson.E{Key: "PersistentId", Value: mpr.IDToBsonBinary(mpr.GenerateID())}) diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index 126ad7c6..c4ef26fd 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "os" "path/filepath" @@ -15,7 +16,8 @@ import ( ) // execCreateImageCollection handles CREATE IMAGE COLLECTION statements. -func (e *Executor) execCreateImageCollection(s *ast.CreateImageCollectionStmt) error { +func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -69,12 +71,18 @@ func (e *Executor) execCreateImageCollection(s *ast.CreateImageCollectionStmt) e // Invalidate hierarchy cache so the new collection's container is visible e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created image collection: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created image collection: %s\n", s.Name) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execCreateImageCollection(s *ast.CreateImageCollectionStmt) error { + return execCreateImageCollection(e.newExecContext(context.Background()), s) +} + // execDropImageCollection handles DROP IMAGE COLLECTION statements. -func (e *Executor) execDropImageCollection(s *ast.DropImageCollectionStmt) error { +func execDropImageCollection(ctx *ExecContext, s *ast.DropImageCollectionStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -88,12 +96,18 @@ func (e *Executor) execDropImageCollection(s *ast.DropImageCollectionStmt) error return mdlerrors.NewBackend("delete image collection", err) } - fmt.Fprintf(e.output, "Dropped image collection: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped image collection: %s\n", s.Name) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execDropImageCollection(s *ast.DropImageCollectionStmt) error { + return execDropImageCollection(e.newExecContext(context.Background()), s) +} + // describeImageCollection handles DESCRIBE IMAGE COLLECTION Module.Name. -func (e *Executor) describeImageCollection(name ast.QualifiedName) error { +func describeImageCollection(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor ic := e.findImageCollection(name.Module, name.Name) if ic == nil { return mdlerrors.NewNotFound("image collection", name.String()) @@ -107,7 +121,7 @@ func (e *Executor) describeImageCollection(name ast.QualifiedName) error { modName := h.GetModuleName(modID) if ic.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", ic.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", ic.Documentation) } exportLevel := ic.ExportLevel @@ -118,12 +132,12 @@ func (e *Executor) describeImageCollection(name ast.QualifiedName) error { qualifiedName := fmt.Sprintf("%s.%s", modName, ic.Name) if len(ic.Images) == 0 { - fmt.Fprintf(e.output, "CREATE OR REPLACE IMAGE COLLECTION %s", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE OR REPLACE IMAGE COLLECTION %s", qualifiedName) if exportLevel != "Hidden" { - fmt.Fprintf(e.output, " EXPORT LEVEL '%s'", exportLevel) + fmt.Fprintf(ctx.Output, " EXPORT LEVEL '%s'", exportLevel) } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") return nil } @@ -133,11 +147,11 @@ func (e *Executor) describeImageCollection(name ast.QualifiedName) error { return mdlerrors.NewBackend("create preview directory", err) } - fmt.Fprintf(e.output, "CREATE OR REPLACE IMAGE COLLECTION %s", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE OR REPLACE IMAGE COLLECTION %s", qualifiedName) if exportLevel != "Hidden" { - fmt.Fprintf(e.output, " EXPORT LEVEL '%s'", exportLevel) + fmt.Fprintf(ctx.Output, " EXPORT LEVEL '%s'", exportLevel) } - fmt.Fprintln(e.output, " (") + fmt.Fprintln(ctx.Output, " (") for i, img := range ic.Images { ext := imageFormatToExt(img.Format) @@ -152,14 +166,19 @@ func (e *Executor) describeImageCollection(name ast.QualifiedName) error { if i == len(ic.Images)-1 { comma = "" } - fmt.Fprintf(e.output, " IMAGE %s FROM FILE '%s'%s\n", img.Name, filePath, comma) + fmt.Fprintf(ctx.Output, " IMAGE %s FROM FILE '%s'%s\n", img.Name, filePath, comma) } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeImageCollection(name ast.QualifiedName) error { + return describeImageCollection(e.newExecContext(context.Background()), name) +} + // imageFormatToExt converts a Mendix ImageFormat value to a file extension. func imageFormatToExt(format string) string { switch format { @@ -197,7 +216,8 @@ func extToImageFormat(ext string) string { } // showImageCollections handles SHOW IMAGE COLLECTION [IN module]. -func (e *Executor) showImageCollections(moduleName string) error { +func showImageCollections(ctx *ExecContext, moduleName string) error { + e := ctx.executor collections, err := e.reader.ListImageCollections() if err != nil { return mdlerrors.NewBackend("list image collections", err) @@ -231,6 +251,11 @@ func (e *Executor) showImageCollections(moduleName string) error { return e.writeResult(result) } +// Executor wrapper for unmigrated callers. +func (e *Executor) showImageCollections(moduleName string) error { + return showImageCollections(e.newExecContext(context.Background()), moduleName) +} + // findImageCollection finds an image collection by module and name. func (e *Executor) findImageCollection(moduleName, collectionName string) *mpr.ImageCollection { collections, err := e.reader.ListImageCollections() diff --git a/mdl/executor/cmd_navigation.go b/mdl/executor/cmd_navigation.go index 590463ab..9fa5a4ab 100644 --- a/mdl/executor/cmd_navigation.go +++ b/mdl/executor/cmd_navigation.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "io" "strings" @@ -14,7 +15,8 @@ import ( // execAlterNavigation handles CREATE [OR REPLACE] NAVIGATION command. // It fully replaces the profile's home pages, login page, not-found page, and menu tree. -func (e *Executor) execAlterNavigation(s *ast.AlterNavigationStmt) error { +func execAlterNavigation(ctx *ExecContext, s *ast.AlterNavigationStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -68,10 +70,15 @@ func (e *Executor) execAlterNavigation(s *ast.AlterNavigationStmt) error { return mdlerrors.NewBackend("update navigation profile", err) } - fmt.Fprintf(e.output, "Navigation profile '%s' updated.\n", s.ProfileName) + fmt.Fprintf(ctx.Output, "Navigation profile '%s' updated.\n", s.ProfileName) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execAlterNavigation(s *ast.AlterNavigationStmt) error { + return execAlterNavigation(e.newExecContext(context.Background()), s) +} + // convertMenuItemDef converts an AST NavMenuItemDef to a writer NavMenuItemSpec. func convertMenuItemDef(def ast.NavMenuItemDef) mpr.NavMenuItemSpec { spec := mpr.NavMenuItemSpec{ @@ -100,14 +107,15 @@ func profileNames(nav *mpr.NavigationDocument) string { // showNavigation handles SHOW NAVIGATION command. // Displays an overview of all navigation profiles with their home pages and menu item counts. -func (e *Executor) showNavigation() error { +func showNavigation(ctx *ExecContext) error { + e := ctx.executor nav, err := e.reader.GetNavigation() if err != nil { return mdlerrors.NewBackend("get navigation", err) } if len(nav.Profiles) == 0 { - fmt.Fprintln(e.output, "No navigation profiles found.") + fmt.Fprintln(ctx.Output, "No navigation profiles found.") return nil } @@ -156,9 +164,15 @@ func (e *Executor) showNavigation() error { return e.writeResult(result) } +// Executor wrapper for unmigrated callers. +func (e *Executor) showNavigation() error { + return showNavigation(e.newExecContext(context.Background())) +} + // showNavigationMenu handles SHOW NAVIGATION MENU [profile] command. // Displays the menu tree for a specific profile, or all profiles if none specified. -func (e *Executor) showNavigationMenu(profileName *ast.QualifiedName) error { +func showNavigationMenu(ctx *ExecContext, profileName *ast.QualifiedName) error { + e := ctx.executor nav, err := e.reader.GetNavigation() if err != nil { return mdlerrors.NewBackend("get navigation", err) @@ -169,43 +183,49 @@ func (e *Executor) showNavigationMenu(profileName *ast.QualifiedName) error { continue } - fmt.Fprintf(e.output, "-- Navigation Menu: %s (%s)\n", p.Name, p.Kind) + fmt.Fprintf(ctx.Output, "-- Navigation Menu: %s (%s)\n", p.Name, p.Kind) if len(p.MenuItems) == 0 { - fmt.Fprintln(e.output, " (no menu items)") + fmt.Fprintln(ctx.Output, " (no menu items)") } else { - printMenuTree(e.output, p.MenuItems, 0) + printMenuTree(ctx.Output, p.MenuItems, 0) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) showNavigationMenu(profileName *ast.QualifiedName) error { + return showNavigationMenu(e.newExecContext(context.Background()), profileName) +} + // showNavigationHomes handles SHOW NAVIGATION HOMES command. // Displays all home page configurations including role-based overrides. -func (e *Executor) showNavigationHomes() error { +func showNavigationHomes(ctx *ExecContext) error { + e := ctx.executor nav, err := e.reader.GetNavigation() if err != nil { return mdlerrors.NewBackend("get navigation", err) } for _, p := range nav.Profiles { - fmt.Fprintf(e.output, "-- Profile: %s (%s)\n", p.Name, p.Kind) + fmt.Fprintf(ctx.Output, "-- Profile: %s (%s)\n", p.Name, p.Kind) // Default home page if p.HomePage != nil { if p.HomePage.Page != "" { - fmt.Fprintf(e.output, " Default Home: PAGE %s\n", p.HomePage.Page) + fmt.Fprintf(ctx.Output, " Default Home: PAGE %s\n", p.HomePage.Page) } else if p.HomePage.Microflow != "" { - fmt.Fprintf(e.output, " Default Home: MICROFLOW %s\n", p.HomePage.Microflow) + fmt.Fprintf(ctx.Output, " Default Home: MICROFLOW %s\n", p.HomePage.Microflow) } } else { - fmt.Fprintln(e.output, " Default Home: (none)") + fmt.Fprintln(ctx.Output, " Default Home: (none)") } // Role-based home pages if len(p.RoleBasedHomePages) > 0 { - fmt.Fprintln(e.output, " Role-Based Homes:") + fmt.Fprintln(ctx.Output, " Role-Based Homes:") for _, rh := range p.RoleBasedHomePages { target := "" if rh.Page != "" { @@ -213,19 +233,25 @@ func (e *Executor) showNavigationHomes() error { } else if rh.Microflow != "" { target = "MICROFLOW " + rh.Microflow } - fmt.Fprintf(e.output, " %s -> %s\n", rh.UserRole, target) + fmt.Fprintf(ctx.Output, " %s -> %s\n", rh.UserRole, target) } } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) showNavigationHomes() error { + return showNavigationHomes(e.newExecContext(context.Background())) +} + // describeNavigation handles DESCRIBE NAVIGATION [profile] command. // Outputs a complete MDL-style description of a navigation profile. -func (e *Executor) describeNavigation(name ast.QualifiedName) error { +func describeNavigation(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor nav, err := e.reader.GetNavigation() if err != nil { return mdlerrors.NewBackend("get navigation", err) @@ -234,7 +260,7 @@ func (e *Executor) describeNavigation(name ast.QualifiedName) error { // If no profile name, describe all profiles if name.Name == "" { for _, p := range nav.Profiles { - e.outputNavigationProfile(p) + outputNavigationProfile(ctx, p) } return nil } @@ -242,7 +268,7 @@ func (e *Executor) describeNavigation(name ast.QualifiedName) error { // Find specific profile for _, p := range nav.Profiles { if strings.EqualFold(p.Name, name.Name) { - e.outputNavigationProfile(p) + outputNavigationProfile(ctx, p) return nil } } @@ -250,65 +276,70 @@ func (e *Executor) describeNavigation(name ast.QualifiedName) error { return mdlerrors.NewNotFound("navigation profile", name.Name) } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeNavigation(name ast.QualifiedName) error { + return describeNavigation(e.newExecContext(context.Background()), name) +} + // outputNavigationProfile outputs a single profile in round-trippable CREATE OR REPLACE NAVIGATION format. -func (e *Executor) outputNavigationProfile(p *mpr.NavigationProfile) { - fmt.Fprintf(e.output, "-- NAVIGATION PROFILE: %s\n", p.Name) - fmt.Fprintf(e.output, "-- Kind: %s\n", p.Kind) +func outputNavigationProfile(ctx *ExecContext, p *mpr.NavigationProfile) { + fmt.Fprintf(ctx.Output, "-- NAVIGATION PROFILE: %s\n", p.Name) + fmt.Fprintf(ctx.Output, "-- Kind: %s\n", p.Kind) if p.IsNative { - fmt.Fprintf(e.output, "-- Native: Yes\n") + fmt.Fprintf(ctx.Output, "-- Native: Yes\n") } - fmt.Fprintf(e.output, "CREATE OR REPLACE NAVIGATION %s\n", p.Name) + fmt.Fprintf(ctx.Output, "CREATE OR REPLACE NAVIGATION %s\n", p.Name) // Home page if p.HomePage != nil { if p.HomePage.Page != "" { - fmt.Fprintf(e.output, " HOME PAGE %s\n", p.HomePage.Page) + fmt.Fprintf(ctx.Output, " HOME PAGE %s\n", p.HomePage.Page) } else if p.HomePage.Microflow != "" { - fmt.Fprintf(e.output, " HOME MICROFLOW %s\n", p.HomePage.Microflow) + fmt.Fprintf(ctx.Output, " HOME MICROFLOW %s\n", p.HomePage.Microflow) } } // Role-based home pages for _, rh := range p.RoleBasedHomePages { if rh.Page != "" { - fmt.Fprintf(e.output, " HOME PAGE %s FOR %s\n", rh.Page, rh.UserRole) + fmt.Fprintf(ctx.Output, " HOME PAGE %s FOR %s\n", rh.Page, rh.UserRole) } else if rh.Microflow != "" { - fmt.Fprintf(e.output, " HOME MICROFLOW %s FOR %s\n", rh.Microflow, rh.UserRole) + fmt.Fprintf(ctx.Output, " HOME MICROFLOW %s FOR %s\n", rh.Microflow, rh.UserRole) } } // Login page if p.LoginPage != "" { - fmt.Fprintf(e.output, " LOGIN PAGE %s\n", p.LoginPage) + fmt.Fprintf(ctx.Output, " LOGIN PAGE %s\n", p.LoginPage) } // Not-found page if p.NotFoundPage != "" { - fmt.Fprintf(e.output, " NOT FOUND PAGE %s\n", p.NotFoundPage) + fmt.Fprintf(ctx.Output, " NOT FOUND PAGE %s\n", p.NotFoundPage) } // Menu items if len(p.MenuItems) > 0 { - fmt.Fprintln(e.output, " MENU (") - printMenuMDL(e.output, p.MenuItems, 2) - fmt.Fprintln(e.output, " )") + fmt.Fprintln(ctx.Output, " MENU (") + printMenuMDL(ctx.Output, p.MenuItems, 2) + fmt.Fprintln(ctx.Output, " )") } // Offline entities (as comments since CREATE NAVIGATION doesn't handle sync yet) if len(p.OfflineEntities) > 0 { - fmt.Fprintln(e.output, " -- Offline Entities (not yet modifiable):") + fmt.Fprintln(ctx.Output, " -- Offline Entities (not yet modifiable):") for _, oe := range p.OfflineEntities { constraint := "" if oe.Constraint != "" { constraint = fmt.Sprintf(" WHERE '%s'", oe.Constraint) } - fmt.Fprintf(e.output, " -- SYNC %s MODE %s%s;\n", oe.Entity, oe.SyncMode, constraint) + fmt.Fprintf(ctx.Output, " -- SYNC %s MODE %s%s;\n", oe.Entity, oe.SyncMode, constraint) } } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output) } // countMenuItems counts the total number of menu items recursively. diff --git a/mdl/executor/cmd_settings.go b/mdl/executor/cmd_settings.go index edcbe66a..1dca694f 100644 --- a/mdl/executor/cmd_settings.go +++ b/mdl/executor/cmd_settings.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strconv" "strings" @@ -13,7 +14,8 @@ import ( ) // showSettings displays an overview table of all settings parts. -func (e *Executor) showSettings() error { +func showSettings(ctx *ExecContext) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -82,8 +84,14 @@ func (e *Executor) showSettings() error { return e.writeResult(tr) } +// Executor wrapper for unmigrated callers. +func (e *Executor) showSettings() error { + return showSettings(e.newExecContext(context.Background())) +} + // describeSettings outputs the full MDL description of all settings. -func (e *Executor) describeSettings() error { +func describeSettings(ctx *ExecContext) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -114,7 +122,7 @@ func (e *Executor) describeSettings() error { if ms.ScheduledEventTimeZoneCode != "" { parts = append(parts, fmt.Sprintf(" ScheduledEventTimeZoneCode = '%s'", ms.ScheduledEventTimeZoneCode)) } - fmt.Fprintf(e.output, "ALTER SETTINGS MODEL\n%s;\n\n", strings.Join(parts, ",\n")) + fmt.Fprintf(ctx.Output, "ALTER SETTINGS MODEL\n%s;\n\n", strings.Join(parts, ",\n")) } // Configuration settings @@ -131,11 +139,11 @@ func (e *Executor) describeSettings() error { if cfg.ApplicationRootUrl != "" { parts = append(parts, fmt.Sprintf(" ApplicationRootUrl = '%s'", cfg.ApplicationRootUrl)) } - fmt.Fprintf(e.output, "ALTER SETTINGS CONFIGURATION '%s'\n%s;\n\n", cfg.Name, strings.Join(parts, ",\n")) + fmt.Fprintf(ctx.Output, "ALTER SETTINGS CONFIGURATION '%s'\n%s;\n\n", cfg.Name, strings.Join(parts, ",\n")) // Output constant overrides for _, cv := range cfg.ConstantValues { - fmt.Fprintf(e.output, "ALTER SETTINGS CONSTANT '%s' VALUE '%s'\n IN CONFIGURATION '%s';\n\n", + fmt.Fprintf(ctx.Output, "ALTER SETTINGS CONSTANT '%s' VALUE '%s'\n IN CONFIGURATION '%s';\n\n", cv.ConstantId, cv.Value, cfg.Name) } } @@ -143,7 +151,7 @@ func (e *Executor) describeSettings() error { // Language settings if ps.Language != nil { - fmt.Fprintf(e.output, "ALTER SETTINGS LANGUAGE\n DefaultLanguageCode = '%s';\n\n", ps.Language.DefaultLanguageCode) + fmt.Fprintf(ctx.Output, "ALTER SETTINGS LANGUAGE\n DefaultLanguageCode = '%s';\n\n", ps.Language.DefaultLanguageCode) } // Workflow settings @@ -160,15 +168,21 @@ func (e *Executor) describeSettings() error { parts = append(parts, fmt.Sprintf(" WorkflowEngineParallelism = %d", ws.WorkflowEngineParallelism)) } if len(parts) > 0 { - fmt.Fprintf(e.output, "ALTER SETTINGS WORKFLOWS\n%s;\n\n", strings.Join(parts, ",\n")) + fmt.Fprintf(ctx.Output, "ALTER SETTINGS WORKFLOWS\n%s;\n\n", strings.Join(parts, ",\n")) } } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeSettings() error { + return describeSettings(e.newExecContext(context.Background())) +} + // alterSettings modifies project settings based on ALTER SETTINGS statement. -func (e *Executor) alterSettings(stmt *ast.AlterSettingsStmt) error { +func alterSettings(ctx *ExecContext, stmt *ast.AlterSettingsStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -249,10 +263,10 @@ func (e *Executor) alterSettings(stmt *ast.AlterSettingsStmt) error { } case "CONFIGURATION": - return e.alterSettingsConfiguration(ps, stmt) + return alterSettingsConfiguration(ctx, ps, stmt) case "CONSTANT": - return e.alterSettingsConstant(ps, stmt) + return alterSettingsConstant(ctx, ps, stmt) default: return mdlerrors.NewUnsupported(fmt.Sprintf("unknown settings section: %s (expected MODEL, CONFIGURATION, CONSTANT, LANGUAGE, or WORKFLOWS)", section)) @@ -263,11 +277,17 @@ func (e *Executor) alterSettings(stmt *ast.AlterSettingsStmt) error { return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Updated %s settings\n", section) + fmt.Fprintf(ctx.Output, "Updated %s settings\n", section) return nil } -func (e *Executor) alterSettingsConfiguration(ps *model.ProjectSettings, stmt *ast.AlterSettingsStmt) error { +// Executor wrapper for unmigrated callers. +func (e *Executor) alterSettings(stmt *ast.AlterSettingsStmt) error { + return alterSettings(e.newExecContext(context.Background()), stmt) +} + +func alterSettingsConfiguration(ctx *ExecContext, ps *model.ProjectSettings, stmt *ast.AlterSettingsStmt) error { + e := ctx.executor if ps.Configuration == nil { return mdlerrors.NewNotFound("settings section", "configuration") } @@ -316,11 +336,12 @@ func (e *Executor) alterSettingsConfiguration(ps *model.ProjectSettings, stmt *a return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Updated configuration '%s'\n", stmt.ConfigName) + fmt.Fprintf(ctx.Output, "Updated configuration '%s'\n", stmt.ConfigName) return nil } -func (e *Executor) alterSettingsConstant(ps *model.ProjectSettings, stmt *ast.AlterSettingsStmt) error { +func alterSettingsConstant(ctx *ExecContext, ps *model.ProjectSettings, stmt *ast.AlterSettingsStmt) error { + e := ctx.executor if ps.Configuration == nil { return mdlerrors.NewNotFound("settings section", "configuration") } @@ -355,7 +376,7 @@ func (e *Executor) alterSettingsConstant(ps *model.ProjectSettings, stmt *ast.Al if err := e.writer.UpdateProjectSettings(ps); err != nil { return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Dropped constant '%s' from configuration '%s'\n", + fmt.Fprintf(ctx.Output, "Dropped constant '%s' from configuration '%s'\n", stmt.ConstantId, targetConfig) return nil } @@ -385,13 +406,14 @@ func (e *Executor) alterSettingsConstant(ps *model.ProjectSettings, stmt *ast.Al return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Updated constant '%s' = '%s' in configuration '%s'\n", + fmt.Fprintf(ctx.Output, "Updated constant '%s' = '%s' in configuration '%s'\n", stmt.ConstantId, stmt.Value, targetConfig) return nil } // createConfiguration handles CREATE CONFIGURATION 'name' [properties...]. -func (e *Executor) createConfiguration(stmt *ast.CreateConfigurationStmt) error { +func createConfiguration(ctx *ExecContext, stmt *ast.CreateConfigurationStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -455,12 +477,18 @@ func (e *Executor) createConfiguration(stmt *ast.CreateConfigurationStmt) error return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Created configuration: %s\n", stmt.Name) + fmt.Fprintf(ctx.Output, "Created configuration: %s\n", stmt.Name) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) createConfiguration(stmt *ast.CreateConfigurationStmt) error { + return createConfiguration(e.newExecContext(context.Background()), stmt) +} + // dropConfiguration handles DROP CONFIGURATION 'name'. -func (e *Executor) dropConfiguration(stmt *ast.DropConfigurationStmt) error { +func dropConfiguration(ctx *ExecContext, stmt *ast.DropConfigurationStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -483,7 +511,7 @@ func (e *Executor) dropConfiguration(stmt *ast.DropConfigurationStmt) error { if err := e.writer.UpdateProjectSettings(ps); err != nil { return mdlerrors.NewBackend("update project settings", err) } - fmt.Fprintf(e.output, "Dropped configuration: %s\n", stmt.Name) + fmt.Fprintf(ctx.Output, "Dropped configuration: %s\n", stmt.Name) return nil } } @@ -491,6 +519,11 @@ func (e *Executor) dropConfiguration(stmt *ast.DropConfigurationStmt) error { return mdlerrors.NewNotFound("configuration", stmt.Name) } +// Executor wrapper for unmigrated callers. +func (e *Executor) dropConfiguration(stmt *ast.DropConfigurationStmt) error { + return dropConfiguration(e.newExecContext(context.Background()), stmt) +} + // settingsValueToString converts an AST settings value to string. func settingsValueToString(val any) string { switch v := val.(type) { diff --git a/mdl/executor/cmd_workflows.go b/mdl/executor/cmd_workflows.go index fdd8d6ae..8594115b 100644 --- a/mdl/executor/cmd_workflows.go +++ b/mdl/executor/cmd_workflows.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -14,7 +15,8 @@ import ( ) // showWorkflows handles SHOW WORKFLOWS command. -func (e *Executor) showWorkflows(moduleName string) error { +func showWorkflows(ctx *ExecContext, moduleName string) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -69,6 +71,11 @@ func (e *Executor) showWorkflows(moduleName string) error { return e.writeResult(result) } +// Executor wrapper for unmigrated callers. +func (e *Executor) showWorkflows(moduleName string) error { + return showWorkflows(e.newExecContext(context.Background()), moduleName) +} + // countWorkflowActivities counts total activities, user tasks, and decisions in a workflow. func countWorkflowActivities(wf *workflows.Workflow) (total, userTasks, decisions int) { if wf.Flow == nil { @@ -131,17 +138,23 @@ func countFlowActivities(flow *workflows.Flow, total, userTasks, decisions *int) } // describeWorkflow handles DESCRIBE WORKFLOW command. -func (e *Executor) describeWorkflow(name ast.QualifiedName) error { - output, _, err := e.describeWorkflowToString(name) +func describeWorkflow(ctx *ExecContext, name ast.QualifiedName) error { + output, _, err := describeWorkflowToString(ctx, name) if err != nil { return err } - fmt.Fprintln(e.output, output) + fmt.Fprintln(ctx.Output, output) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeWorkflow(name ast.QualifiedName) error { + return describeWorkflow(e.newExecContext(context.Background()), name) +} + // describeWorkflowToString generates MDL-like output for a workflow and returns it as a string. -func (e *Executor) describeWorkflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { +func describeWorkflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return "", nil, mdlerrors.NewBackend("build hierarchy", err) @@ -234,6 +247,11 @@ func (e *Executor) describeWorkflowToString(name ast.QualifiedName) (string, map return strings.Join(lines, "\n"), nil, nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeWorkflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + return describeWorkflowToString(e.newExecContext(context.Background()), name) +} + // formatAnnotation returns an ANNOTATION statement for a workflow activity annotation. // The annotation is emitted as a parseable MDL statement so it survives round-trips. func formatAnnotation(annotation string, indent string) string { diff --git a/mdl/executor/cmd_workflows_write.go b/mdl/executor/cmd_workflows_write.go index 0694ee5e..5c55a69a 100644 --- a/mdl/executor/cmd_workflows_write.go +++ b/mdl/executor/cmd_workflows_write.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" "unicode" @@ -16,7 +17,8 @@ import ( ) // execCreateWorkflow handles CREATE WORKFLOW statements. -func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { +func execCreateWorkflow(ctx *ExecContext, s *ast.CreateWorkflowStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -98,7 +100,7 @@ func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { userActivities := buildWorkflowActivities(s.Activities) // Auto-bind microflow/workflow parameters and sanitize names - e.autoBindWorkflowParameters(userActivities) + autoBindWorkflowParameters(e, userActivities) // Deduplicate activity names to avoid CE0495 deduplicateActivityNames(userActivities) @@ -123,12 +125,18 @@ func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created workflow: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { + return execCreateWorkflow(e.newExecContext(context.Background()), s) +} + // execDropWorkflow handles DROP WORKFLOW statements. -func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { +func execDropWorkflow(ctx *ExecContext, s *ast.DropWorkflowStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -151,7 +159,7 @@ func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { return mdlerrors.NewBackend("delete workflow", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped workflow: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } } @@ -159,6 +167,11 @@ func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { return mdlerrors.NewNotFound("workflow", s.Name.Module+"."+s.Name.Name) } +// Executor wrapper for unmigrated callers. +func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { + return execDropWorkflow(e.newExecContext(context.Background()), s) +} + // generateWorkflowUUID generates a UUID for workflow elements. func generateWorkflowUUID() string { return mpr.GenerateID() @@ -602,36 +615,36 @@ func sanitizeActivityName(name string) string { // autoBindWorkflowParameters resolves microflow/workflow parameters and generates // ParameterMappings, default outcomes, and sanitized names for workflow activities. -func (e *Executor) autoBindWorkflowParameters(activities []workflows.WorkflowActivity) { - e.autoBindActivitiesInFlow(activities) +func autoBindWorkflowParameters(e *Executor, activities []workflows.WorkflowActivity) { + autoBindActivitiesInFlow(e, activities) } -func (e *Executor) autoBindActivitiesInFlow(activities []workflows.WorkflowActivity) { +func autoBindActivitiesInFlow(e *Executor, activities []workflows.WorkflowActivity) { for _, act := range activities { switch a := act.(type) { case *workflows.CallMicroflowTask: - e.autoBindCallMicroflow(a) + autoBindCallMicroflow(e, a) // Recurse into outcomes for _, outcome := range a.Outcomes { switch o := outcome.(type) { case *workflows.BooleanConditionOutcome: if o.Flow != nil { - e.autoBindActivitiesInFlow(o.Flow.Activities) + autoBindActivitiesInFlow(e, o.Flow.Activities) } case *workflows.VoidConditionOutcome: if o.Flow != nil { - e.autoBindActivitiesInFlow(o.Flow.Activities) + autoBindActivitiesInFlow(e, o.Flow.Activities) } } } case *workflows.CallWorkflowActivity: - e.autoBindCallWorkflow(a) + autoBindCallWorkflow(e, a) case *workflows.UserTask: // Sanitize name a.Name = sanitizeActivityName(a.Name) for _, outcome := range a.Outcomes { if outcome.Flow != nil { - e.autoBindActivitiesInFlow(outcome.Flow.Activities) + autoBindActivitiesInFlow(e, outcome.Flow.Activities) } } case *workflows.ParallelSplitActivity: @@ -639,7 +652,7 @@ func (e *Executor) autoBindActivitiesInFlow(activities []workflows.WorkflowActiv a.Name = sanitizeActivityName(a.Name) for _, outcome := range a.Outcomes { if outcome.Flow != nil { - e.autoBindActivitiesInFlow(outcome.Flow.Activities) + autoBindActivitiesInFlow(e, outcome.Flow.Activities) } } case *workflows.ExclusiveSplitActivity: @@ -648,11 +661,11 @@ func (e *Executor) autoBindActivitiesInFlow(activities []workflows.WorkflowActiv switch o := outcome.(type) { case *workflows.BooleanConditionOutcome: if o.Flow != nil { - e.autoBindActivitiesInFlow(o.Flow.Activities) + autoBindActivitiesInFlow(e, o.Flow.Activities) } case *workflows.VoidConditionOutcome: if o.Flow != nil { - e.autoBindActivitiesInFlow(o.Flow.Activities) + autoBindActivitiesInFlow(e, o.Flow.Activities) } } } @@ -667,7 +680,7 @@ func (e *Executor) autoBindActivitiesInFlow(activities []workflows.WorkflowActiv } // autoBindCallMicroflow resolves microflow parameters and auto-generates ParameterMappings. -func (e *Executor) autoBindCallMicroflow(task *workflows.CallMicroflowTask) { +func autoBindCallMicroflow(e *Executor, task *workflows.CallMicroflowTask) { // Sanitize name task.Name = sanitizeActivityName(task.Name) @@ -720,7 +733,7 @@ func (e *Executor) autoBindCallMicroflow(task *workflows.CallMicroflowTask) { } // autoBindCallWorkflow resolves workflow parameters and generates ParameterMappings. -func (e *Executor) autoBindCallWorkflow(act *workflows.CallWorkflowActivity) { +func autoBindCallWorkflow(e *Executor, act *workflows.CallWorkflowActivity) { // Sanitize name act.Name = sanitizeActivityName(act.Name) diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index b12ef9f8..81e36f10 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -53,7 +53,7 @@ func registerConstantHandlers(r *Registry) { func registerDatabaseConnectionHandlers(r *Registry) { r.Register(&ast.CreateDatabaseConnectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createDatabaseConnection(stmt.(*ast.CreateDatabaseConnectionStmt)) + return createDatabaseConnection(ctx, stmt.(*ast.CreateDatabaseConnectionStmt)) }) } @@ -182,70 +182,70 @@ func registerSecurityHandlers(r *Registry) { func registerNavigationHandlers(r *Registry) { r.Register(&ast.AlterNavigationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterNavigation(stmt.(*ast.AlterNavigationStmt)) + return execAlterNavigation(ctx, stmt.(*ast.AlterNavigationStmt)) }) } func registerImageHandlers(r *Registry) { r.Register(&ast.CreateImageCollectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateImageCollection(stmt.(*ast.CreateImageCollectionStmt)) + return execCreateImageCollection(ctx, stmt.(*ast.CreateImageCollectionStmt)) }) r.Register(&ast.DropImageCollectionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropImageCollection(stmt.(*ast.DropImageCollectionStmt)) + return execDropImageCollection(ctx, stmt.(*ast.DropImageCollectionStmt)) }) } func registerWorkflowHandlers(r *Registry) { r.Register(&ast.CreateWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateWorkflow(stmt.(*ast.CreateWorkflowStmt)) + return execCreateWorkflow(ctx, stmt.(*ast.CreateWorkflowStmt)) }) r.Register(&ast.DropWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropWorkflow(stmt.(*ast.DropWorkflowStmt)) + return execDropWorkflow(ctx, stmt.(*ast.DropWorkflowStmt)) }) r.Register(&ast.AlterWorkflowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterWorkflow(stmt.(*ast.AlterWorkflowStmt)) + return execAlterWorkflow(ctx, stmt.(*ast.AlterWorkflowStmt)) }) } func registerBusinessEventHandlers(r *Registry) { r.Register(&ast.CreateBusinessEventServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createBusinessEventService(stmt.(*ast.CreateBusinessEventServiceStmt)) + return createBusinessEventService(ctx, stmt.(*ast.CreateBusinessEventServiceStmt)) }) r.Register(&ast.DropBusinessEventServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropBusinessEventService(stmt.(*ast.DropBusinessEventServiceStmt)) + return dropBusinessEventService(ctx, stmt.(*ast.DropBusinessEventServiceStmt)) }) } func registerSettingsHandlers(r *Registry) { r.Register(&ast.AlterSettingsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.alterSettings(stmt.(*ast.AlterSettingsStmt)) + return alterSettings(ctx, stmt.(*ast.AlterSettingsStmt)) }) r.Register(&ast.CreateConfigurationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createConfiguration(stmt.(*ast.CreateConfigurationStmt)) + return createConfiguration(ctx, stmt.(*ast.CreateConfigurationStmt)) }) r.Register(&ast.DropConfigurationStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropConfiguration(stmt.(*ast.DropConfigurationStmt)) + return dropConfiguration(ctx, stmt.(*ast.DropConfigurationStmt)) }) } func registerODataHandlers(r *Registry) { r.Register(&ast.CreateODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createODataClient(stmt.(*ast.CreateODataClientStmt)) + return createODataClient(ctx, stmt.(*ast.CreateODataClientStmt)) }) r.Register(&ast.AlterODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.alterODataClient(stmt.(*ast.AlterODataClientStmt)) + return alterODataClient(ctx, stmt.(*ast.AlterODataClientStmt)) }) r.Register(&ast.DropODataClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropODataClient(stmt.(*ast.DropODataClientStmt)) + return dropODataClient(ctx, stmt.(*ast.DropODataClientStmt)) }) r.Register(&ast.CreateODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createODataService(stmt.(*ast.CreateODataServiceStmt)) + return createODataService(ctx, stmt.(*ast.CreateODataServiceStmt)) }) r.Register(&ast.AlterODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.alterODataService(stmt.(*ast.AlterODataServiceStmt)) + return alterODataService(ctx, stmt.(*ast.AlterODataServiceStmt)) }) r.Register(&ast.DropODataServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropODataService(stmt.(*ast.DropODataServiceStmt)) + return dropODataService(ctx, stmt.(*ast.DropODataServiceStmt)) }) } @@ -275,22 +275,22 @@ func registerMappingHandlers(r *Registry) { func registerRESTHandlers(r *Registry) { r.Register(&ast.CreateRestClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createRestClient(stmt.(*ast.CreateRestClientStmt)) + return createRestClient(ctx, stmt.(*ast.CreateRestClientStmt)) }) r.Register(&ast.DropRestClientStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.dropRestClient(stmt.(*ast.DropRestClientStmt)) + return dropRestClient(ctx, stmt.(*ast.DropRestClientStmt)) }) r.Register(&ast.CreatePublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreatePublishedRestService(stmt.(*ast.CreatePublishedRestServiceStmt)) + return execCreatePublishedRestService(ctx, stmt.(*ast.CreatePublishedRestServiceStmt)) }) r.Register(&ast.DropPublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropPublishedRestService(stmt.(*ast.DropPublishedRestServiceStmt)) + return execDropPublishedRestService(ctx, stmt.(*ast.DropPublishedRestServiceStmt)) }) r.Register(&ast.AlterPublishedRestServiceStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterPublishedRestService(stmt.(*ast.AlterPublishedRestServiceStmt)) + return execAlterPublishedRestService(ctx, stmt.(*ast.AlterPublishedRestServiceStmt)) }) r.Register(&ast.CreateExternalEntityStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateExternalEntity(stmt.(*ast.CreateExternalEntityStmt)) + return execCreateExternalEntity(ctx, stmt.(*ast.CreateExternalEntityStmt)) }) r.Register(&ast.CreateExternalEntitiesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return ctx.executor.createExternalEntities(stmt.(*ast.CreateExternalEntitiesStmt)) @@ -311,10 +311,10 @@ func registerRESTHandlers(r *Registry) { func registerDataTransformerHandlers(r *Registry) { r.Register(&ast.CreateDataTransformerStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateDataTransformer(stmt.(*ast.CreateDataTransformerStmt)) + return execCreateDataTransformer(ctx, stmt.(*ast.CreateDataTransformerStmt)) }) r.Register(&ast.DropDataTransformerStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropDataTransformer(stmt.(*ast.DropDataTransformerStmt)) + return execDropDataTransformer(ctx, stmt.(*ast.DropDataTransformerStmt)) }) } From f31a83dc48eb117eb2b20577318097d8b6edc9ed Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:38:57 +0200 Subject: [PATCH 10/16] refactor: migrate OData, REST, business event, database, and data transformer handlers to free functions --- mdl/executor/cmd_businessevents.go | 75 +++++++--- mdl/executor/cmd_datatransformer.go | 35 +++-- mdl/executor/cmd_dbconnection.go | 79 ++++++---- mdl/executor/cmd_odata.go | 215 ++++++++++++++++++---------- mdl/executor/cmd_published_rest.go | 75 ++++++---- mdl/executor/cmd_rest_clients.go | 39 +++-- 6 files changed, 349 insertions(+), 169 deletions(-) diff --git a/mdl/executor/cmd_businessevents.go b/mdl/executor/cmd_businessevents.go index 9844d33a..35f041c3 100644 --- a/mdl/executor/cmd_businessevents.go +++ b/mdl/executor/cmd_businessevents.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -13,7 +14,9 @@ import ( ) // showBusinessEventServices displays a table of all business event service documents. -func (e *Executor) showBusinessEventServices(inModule string) error { +func showBusinessEventServices(ctx *ExecContext, inModule string) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -40,9 +43,9 @@ func (e *Executor) showBusinessEventServices(inModule string) error { if len(filtered) == 0 { if inModule != "" { - fmt.Fprintf(e.output, "No business event services found in module %s\n", inModule) + fmt.Fprintf(ctx.Output, "No business event services found in module %s\n", inModule) } else { - fmt.Fprintln(e.output, "No business event services found") + fmt.Fprintln(ctx.Output, "No business event services found") } return nil } @@ -87,13 +90,15 @@ func (e *Executor) showBusinessEventServices(inModule string) error { } // showBusinessEventClients displays a table of all business event client documents. -func (e *Executor) showBusinessEventClients(inModule string) error { - fmt.Fprintln(e.output, "Business event clients are not yet implemented.") +func showBusinessEventClients(ctx *ExecContext, inModule string) error { + fmt.Fprintln(ctx.Output, "Business event clients are not yet implemented.") return nil } // showBusinessEvents displays a table of individual messages across all business event services. -func (e *Executor) showBusinessEvents(inModule string) error { +func showBusinessEvents(ctx *ExecContext, inModule string) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -152,9 +157,9 @@ func (e *Executor) showBusinessEvents(inModule string) error { if len(rows) == 0 { if inModule != "" { - fmt.Fprintf(e.output, "No business events found in module %s\n", inModule) + fmt.Fprintf(ctx.Output, "No business events found in module %s\n", inModule) } else { - fmt.Fprintln(e.output, "No business events found") + fmt.Fprintln(ctx.Output, "No business events found") } return nil } @@ -170,7 +175,9 @@ func (e *Executor) showBusinessEvents(inModule string) error { } // describeBusinessEventService outputs the full MDL description of a business event service. -func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { +func describeBusinessEventService(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -205,21 +212,21 @@ func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { // Output MDL CREATE statement if found.Documentation != "" { - outputJavadoc(e.output, found.Documentation) + outputJavadoc(ctx.Output, found.Documentation) } - fmt.Fprintf(e.output, "CREATE OR REPLACE BUSINESS EVENT SERVICE %s.%s\n", foundModule, found.Name) + fmt.Fprintf(ctx.Output, "CREATE OR REPLACE BUSINESS EVENT SERVICE %s.%s\n", foundModule, found.Name) if found.Definition != nil { - fmt.Fprintf(e.output, "(\n") - fmt.Fprintf(e.output, " ServiceName: '%s'", found.Definition.ServiceName) + fmt.Fprintf(ctx.Output, "(\n") + fmt.Fprintf(ctx.Output, " ServiceName: '%s'", found.Definition.ServiceName) if found.Definition.EventNamePrefix != "" { - fmt.Fprintf(e.output, ",\n EventNamePrefix: '%s'", found.Definition.EventNamePrefix) + fmt.Fprintf(ctx.Output, ",\n EventNamePrefix: '%s'", found.Definition.EventNamePrefix) } else { - fmt.Fprintf(e.output, ",\n EventNamePrefix: ''") + fmt.Fprintf(ctx.Output, ",\n EventNamePrefix: ''") } - fmt.Fprintf(e.output, "\n)\n") + fmt.Fprintf(ctx.Output, "\n)\n") - fmt.Fprintf(e.output, "{\n") + fmt.Fprintf(ctx.Output, "{\n") // Build operation map: messageName -> operation info opMap := make(map[string]*model.ServiceOperation) @@ -248,19 +255,21 @@ func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { } } - fmt.Fprintf(e.output, " MESSAGE %s (%s) %s%s;\n", + fmt.Fprintf(ctx.Output, " MESSAGE %s (%s) %s%s;\n", msg.MessageName, strings.Join(attrs, ", "), opStr, entityStr) } } - fmt.Fprintf(e.output, "};\n") + fmt.Fprintf(ctx.Output, "};\n") } return nil } // createBusinessEventService creates a new business event service from an AST statement. -func (e *Executor) createBusinessEventService(stmt *ast.CreateBusinessEventServiceStmt) error { +func createBusinessEventService(ctx *ExecContext, stmt *ast.CreateBusinessEventServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -369,12 +378,14 @@ func (e *Executor) createBusinessEventService(stmt *ast.CreateBusinessEventServi return mdlerrors.NewBackend("create business event service", err) } - fmt.Fprintf(e.output, "Created business event service: %s.%s\n", moduleName, stmt.Name.Name) + fmt.Fprintf(ctx.Output, "Created business event service: %s.%s\n", moduleName, stmt.Name.Name) return nil } // dropBusinessEventService deletes a business event service. -func (e *Executor) dropBusinessEventService(stmt *ast.DropBusinessEventServiceStmt) error { +func dropBusinessEventService(ctx *ExecContext, stmt *ast.DropBusinessEventServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -396,7 +407,7 @@ func (e *Executor) dropBusinessEventService(stmt *ast.DropBusinessEventServiceSt if err := e.writer.DeleteBusinessEventService(svc.ID); err != nil { return mdlerrors.NewBackend("delete business event service", err) } - fmt.Fprintf(e.output, "Dropped business event service: %s.%s\n", moduleName, svc.Name) + fmt.Fprintf(ctx.Output, "Dropped business event service: %s.%s\n", moduleName, svc.Name) return nil } } @@ -410,3 +421,21 @@ func generateChannelName() string { uuid := mpr.GenerateID() return strings.ReplaceAll(uuid, "-", "") } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) showBusinessEventServices(inModule string) error { + return showBusinessEventServices(e.newExecContext(context.Background()), inModule) +} + +func (e *Executor) showBusinessEventClients(inModule string) error { + return showBusinessEventClients(e.newExecContext(context.Background()), inModule) +} + +func (e *Executor) showBusinessEvents(inModule string) error { + return showBusinessEvents(e.newExecContext(context.Background()), inModule) +} + +func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { + return describeBusinessEventService(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_datatransformer.go b/mdl/executor/cmd_datatransformer.go index 77c919cb..5cd19993 100644 --- a/mdl/executor/cmd_datatransformer.go +++ b/mdl/executor/cmd_datatransformer.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -12,7 +13,9 @@ import ( ) // listDataTransformers handles LIST DATA TRANSFORMERS [IN module]. -func (e *Executor) listDataTransformers(moduleName string) error { +func listDataTransformers(ctx *ExecContext, moduleName string) error { + e := ctx.executor + transformers, err := e.reader.ListDataTransformers() if err != nil { return mdlerrors.NewBackend("list data transformers", err) @@ -42,7 +45,7 @@ func (e *Executor) listDataTransformers(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No data transformers found.") + fmt.Fprintln(ctx.Output, "No data transformers found.") return nil } @@ -55,7 +58,9 @@ func (e *Executor) listDataTransformers(moduleName string) error { } // describeDataTransformer handles DESCRIBE DATA TRANSFORMER Module.Name. -func (e *Executor) describeDataTransformer(name ast.QualifiedName) error { +func describeDataTransformer(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + transformers, err := e.reader.ListDataTransformers() if err != nil { return mdlerrors.NewBackend("list data transformers", err) @@ -73,7 +78,7 @@ func (e *Executor) describeDataTransformer(name ast.QualifiedName) error { continue } - w := e.output + w := ctx.Output // Emit re-executable MDL fmt.Fprintf(w, "CREATE DATA TRANSFORMER %s.%s\n", modName, dt.Name) @@ -103,7 +108,9 @@ func (e *Executor) describeDataTransformer(name ast.QualifiedName) error { } // execCreateDataTransformer creates a new data transformer. -func (e *Executor) execCreateDataTransformer(s *ast.CreateDataTransformerStmt) error { +func execCreateDataTransformer(ctx *ExecContext, s *ast.CreateDataTransformerStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -138,14 +145,16 @@ func (e *Executor) execCreateDataTransformer(s *ast.CreateDataTransformerStmt) e } if !e.quiet { - fmt.Fprintf(e.output, "Created data transformer: %s.%s (%d steps)\n", + fmt.Fprintf(ctx.Output, "Created data transformer: %s.%s (%d steps)\n", s.Name.Module, s.Name.Name, len(dt.Steps)) } return nil } // execDropDataTransformer deletes a data transformer. -func (e *Executor) execDropDataTransformer(s *ast.DropDataTransformerStmt) error { +func execDropDataTransformer(ctx *ExecContext, s *ast.DropDataTransformerStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -168,7 +177,7 @@ func (e *Executor) execDropDataTransformer(s *ast.DropDataTransformerStmt) error return mdlerrors.NewBackend("drop data transformer", err) } if !e.quiet { - fmt.Fprintf(e.output, "Dropped data transformer: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped data transformer: %s.%s\n", s.Name.Module, s.Name.Name) } return nil } @@ -176,3 +185,13 @@ func (e *Executor) execDropDataTransformer(s *ast.DropDataTransformerStmt) error return mdlerrors.NewNotFound("data transformer", s.Name.Module+"."+s.Name.Name) } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) listDataTransformers(moduleName string) error { + return listDataTransformers(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeDataTransformer(name ast.QualifiedName) error { + return describeDataTransformer(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_dbconnection.go b/mdl/executor/cmd_dbconnection.go index 429a9f56..366b3ba1 100644 --- a/mdl/executor/cmd_dbconnection.go +++ b/mdl/executor/cmd_dbconnection.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -13,7 +14,9 @@ import ( ) // createDatabaseConnection handles CREATE DATABASE CONNECTION command. -func (e *Executor) createDatabaseConnection(stmt *ast.CreateDatabaseConnectionStmt) error { +func createDatabaseConnection(ctx *ExecContext, stmt *ast.CreateDatabaseConnectionStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -53,7 +56,7 @@ func (e *Executor) createDatabaseConnection(stmt *ast.CreateDatabaseConnectionSt // Resolve ConnectionInput.Value from constant default (for Studio Pro dev) connInputValue := "" if stmt.ConnectionStringIsRef { - connInputValue = e.resolveConstantDefault(connStr) + connInputValue = resolveConstantDefault(ctx, connStr) } conn := &model.DatabaseConnection{ @@ -116,12 +119,14 @@ func (e *Executor) createDatabaseConnection(stmt *ast.CreateDatabaseConnectionSt } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created database connection: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + fmt.Fprintf(ctx.Output, "Created database connection: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } // showDatabaseConnections handles SHOW DATABASE CONNECTIONS command. -func (e *Executor) showDatabaseConnections(moduleName string) error { +func showDatabaseConnections(ctx *ExecContext, moduleName string) error { + e := ctx.executor + connections, err := e.reader.ListDatabaseConnections() if err != nil { return mdlerrors.NewBackend("list database connections", err) @@ -156,7 +161,7 @@ func (e *Executor) showDatabaseConnections(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No database connections found.") + fmt.Fprintln(ctx.Output, "No database connections found.") return nil } @@ -175,7 +180,9 @@ func (e *Executor) showDatabaseConnections(moduleName string) error { } // describeDatabaseConnection handles DESCRIBE DATABASE CONNECTION command. -func (e *Executor) describeDatabaseConnection(name ast.QualifiedName) error { +func describeDatabaseConnection(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + connections, err := e.reader.ListDatabaseConnections() if err != nil { return mdlerrors.NewBackend("list database connections", err) @@ -190,7 +197,7 @@ func (e *Executor) describeDatabaseConnection(name ast.QualifiedName) error { modID := h.FindModuleID(conn.ContainerID) modName := h.GetModuleName(modID) if strings.EqualFold(modName, name.Module) && strings.EqualFold(conn.Name, name.Name) { - return e.outputDatabaseConnectionMDL(conn, modName) + return outputDatabaseConnectionMDL(ctx, conn, modName) } } @@ -198,52 +205,52 @@ func (e *Executor) describeDatabaseConnection(name ast.QualifiedName) error { } // outputDatabaseConnectionMDL outputs a database connection definition in MDL format. -func (e *Executor) outputDatabaseConnectionMDL(conn *model.DatabaseConnection, moduleName string) error { - fmt.Fprintf(e.output, "CREATE DATABASE CONNECTION %s.%s\n", moduleName, conn.Name) - fmt.Fprintf(e.output, "TYPE '%s'\n", conn.DatabaseType) +func outputDatabaseConnectionMDL(ctx *ExecContext, conn *model.DatabaseConnection, moduleName string) error { + fmt.Fprintf(ctx.Output, "CREATE DATABASE CONNECTION %s.%s\n", moduleName, conn.Name) + fmt.Fprintf(ctx.Output, "TYPE '%s'\n", conn.DatabaseType) // Connection string - fmt.Fprintf(e.output, "CONNECTION STRING @%s\n", conn.ConnectionString) + fmt.Fprintf(ctx.Output, "CONNECTION STRING @%s\n", conn.ConnectionString) // Username - fmt.Fprintf(e.output, "USERNAME @%s\n", conn.UserName) + fmt.Fprintf(ctx.Output, "USERNAME @%s\n", conn.UserName) // Password - fmt.Fprintf(e.output, "PASSWORD @%s\n", conn.Password) + fmt.Fprintf(ctx.Output, "PASSWORD @%s\n", conn.Password) // Queries if len(conn.Queries) > 0 { - fmt.Fprintln(e.output, "BEGIN") + fmt.Fprintln(ctx.Output, "BEGIN") for _, q := range conn.Queries { - fmt.Fprintf(e.output, " QUERY %s\n", q.Name) + fmt.Fprintf(ctx.Output, " QUERY %s\n", q.Name) // SQL string if q.SQL != "" { escaped := strings.ReplaceAll(q.SQL, "'", "''") - fmt.Fprintf(e.output, " SQL '%s'\n", escaped) + fmt.Fprintf(ctx.Output, " SQL '%s'\n", escaped) } // PARAMETER clauses for _, p := range q.Parameters { typeName := dbTypeToMDLType(p.DataType) if p.EmptyValueBecomesNull { - fmt.Fprintf(e.output, " PARAMETER %s: %s NULL\n", p.ParameterName, typeName) + fmt.Fprintf(ctx.Output, " PARAMETER %s: %s NULL\n", p.ParameterName, typeName) } else if p.DefaultValue != "" { escaped := strings.ReplaceAll(p.DefaultValue, "'", "''") - fmt.Fprintf(e.output, " PARAMETER %s: %s DEFAULT '%s'\n", p.ParameterName, typeName, escaped) + fmt.Fprintf(ctx.Output, " PARAMETER %s: %s DEFAULT '%s'\n", p.ParameterName, typeName, escaped) } else { - fmt.Fprintf(e.output, " PARAMETER %s: %s\n", p.ParameterName, typeName) + fmt.Fprintf(ctx.Output, " PARAMETER %s: %s\n", p.ParameterName, typeName) } } // RETURNS and MAP from table mapping if len(q.TableMappings) > 0 { tm := q.TableMappings[0] - fmt.Fprintf(e.output, " RETURNS %s\n", tm.Entity) + fmt.Fprintf(ctx.Output, " RETURNS %s\n", tm.Entity) // MAP clause if len(tm.Columns) > 0 { - fmt.Fprintln(e.output, " MAP (") + fmt.Fprintln(ctx.Output, " MAP (") for i, c := range tm.Columns { // Extract attribute name from qualified ref (Module.Entity.Attr → Attr) attrName := c.Attribute @@ -254,24 +261,26 @@ func (e *Executor) outputDatabaseConnectionMDL(conn *model.DatabaseConnection, m if i == len(tm.Columns)-1 { sep = "" } - fmt.Fprintf(e.output, " %s AS %s%s\n", c.ColumnName, attrName, sep) + fmt.Fprintf(ctx.Output, " %s AS %s%s\n", c.ColumnName, attrName, sep) } - fmt.Fprintln(e.output, " )") + fmt.Fprintln(ctx.Output, " )") } } - fmt.Fprintln(e.output, " ;") + fmt.Fprintln(ctx.Output, " ;") } - fmt.Fprintln(e.output, "END") + fmt.Fprintln(ctx.Output, "END") } - fmt.Fprintln(e.output, ";") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ";") + fmt.Fprintln(ctx.Output, "/") return nil } // resolveConstantDefault looks up a constant by qualified name and returns its default value. -func (e *Executor) resolveConstantDefault(qualifiedName string) string { +func resolveConstantDefault(ctx *ExecContext, qualifiedName string) string { + e := ctx.executor + constants, err := e.reader.ListConstants() if err != nil { return "" @@ -328,3 +337,17 @@ func dbTypeToMDLType(bsonType string) string { return "String" } } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) showDatabaseConnections(moduleName string) error { + return showDatabaseConnections(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeDatabaseConnection(name ast.QualifiedName) error { + return describeDatabaseConnection(e.newExecContext(context.Background()), name) +} + +func (e *Executor) outputDatabaseConnectionMDL(conn *model.DatabaseConnection, moduleName string) error { + return outputDatabaseConnectionMDL(e.newExecContext(context.Background()), conn, moduleName) +} diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index 95b84626..e78b901d 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -3,6 +3,7 @@ package executor import ( + "context" "crypto/sha256" "fmt" "io" @@ -35,7 +36,9 @@ func outputJavadocIndented(w io.Writer, text string, indent string) { } // showODataClients handles SHOW ODATA CLIENTS [IN module] command. -func (e *Executor) showODataClients(moduleName string) error { +func showODataClients(ctx *ExecContext, moduleName string) error { + e := ctx.executor + services, err := e.reader.ListConsumedODataServices() if err != nil { return mdlerrors.NewBackend("list consumed OData services", err) @@ -78,7 +81,7 @@ func (e *Executor) showODataClients(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No consumed OData services found.") + fmt.Fprintln(ctx.Output, "No consumed OData services found.") return nil } @@ -98,7 +101,9 @@ func (e *Executor) showODataClients(moduleName string) error { } // describeODataClient handles DESCRIBE ODATA CLIENT command. -func (e *Executor) describeODataClient(name ast.QualifiedName) error { +func describeODataClient(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + services, err := e.reader.ListConsumedODataServices() if err != nil { return mdlerrors.NewBackend("list consumed OData services", err) @@ -114,7 +119,7 @@ func (e *Executor) describeODataClient(name ast.QualifiedName) error { modName := h.GetModuleName(modID) if strings.EqualFold(modName, name.Module) && strings.EqualFold(svc.Name, name.Name) { folderPath := h.BuildFolderPath(svc.ContainerID) - return e.outputConsumedODataServiceMDL(svc, modName, folderPath) + return outputConsumedODataServiceMDL(ctx, svc, modName, folderPath) } } @@ -122,13 +127,13 @@ func (e *Executor) describeODataClient(name ast.QualifiedName) error { } // outputConsumedODataServiceMDL outputs a consumed OData service in MDL format. -func (e *Executor) outputConsumedODataServiceMDL(svc *model.ConsumedODataService, moduleName string, folderPath string) error { +func outputConsumedODataServiceMDL(ctx *ExecContext, svc *model.ConsumedODataService, moduleName string, folderPath string) error { // Use Description for javadoc (the user-visible API description) if svc.Description != "" { - outputJavadoc(e.output, svc.Description) + outputJavadoc(ctx.Output, svc.Description) } - fmt.Fprintf(e.output, "CREATE ODATA CLIENT %s.%s (\n", moduleName, svc.Name) + fmt.Fprintf(ctx.Output, "CREATE ODATA CLIENT %s.%s (\n", moduleName, svc.Name) var props []string if folderPath != "" { @@ -191,31 +196,33 @@ func (e *Executor) outputConsumedODataServiceMDL(svc *model.ConsumedODataService props = append(props, fmt.Sprintf(" ProxyPassword: %s", svc.ProxyPassword)) } - fmt.Fprintln(e.output, strings.Join(props, ",\n")) + fmt.Fprintln(ctx.Output, strings.Join(props, ",\n")) // Custom HTTP headers (between property block close and semicolon) if cfg := svc.HttpConfiguration; cfg != nil && len(cfg.HeaderEntries) > 0 { - fmt.Fprintln(e.output, ")") - fmt.Fprintln(e.output, "HEADERS (") + fmt.Fprintln(ctx.Output, ")") + fmt.Fprintln(ctx.Output, "HEADERS (") for i, h := range cfg.HeaderEntries { comma := "," if i == len(cfg.HeaderEntries)-1 { comma = "" } - fmt.Fprintf(e.output, " '%s': %s%s\n", h.Key, formatExprValue(h.Value), comma) + fmt.Fprintf(ctx.Output, " '%s': %s%s\n", h.Key, formatExprValue(h.Value), comma) } - fmt.Fprintln(e.output, ");") + fmt.Fprintln(ctx.Output, ");") } else { - fmt.Fprintln(e.output, ");") + fmt.Fprintln(ctx.Output, ");") } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } // showODataServices handles SHOW ODATA SERVICES [IN module] command. -func (e *Executor) showODataServices(moduleName string) error { +func showODataServices(ctx *ExecContext, moduleName string) error { + e := ctx.executor + services, err := e.reader.ListPublishedODataServices() if err != nil { return mdlerrors.NewBackend("list published OData services", err) @@ -255,7 +262,7 @@ func (e *Executor) showODataServices(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No published OData services found.") + fmt.Fprintln(ctx.Output, "No published OData services found.") return nil } @@ -275,7 +282,9 @@ func (e *Executor) showODataServices(moduleName string) error { } // describeODataService handles DESCRIBE ODATA SERVICE command. -func (e *Executor) describeODataService(name ast.QualifiedName) error { +func describeODataService(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + services, err := e.reader.ListPublishedODataServices() if err != nil { return mdlerrors.NewBackend("list published OData services", err) @@ -291,7 +300,7 @@ func (e *Executor) describeODataService(name ast.QualifiedName) error { modName := h.GetModuleName(modID) if strings.EqualFold(modName, name.Module) && strings.EqualFold(svc.Name, name.Name) { folderPath := h.BuildFolderPath(svc.ContainerID) - return e.outputPublishedODataServiceMDL(svc, modName, folderPath) + return outputPublishedODataServiceMDL(ctx, svc, modName, folderPath) } } @@ -299,13 +308,13 @@ func (e *Executor) describeODataService(name ast.QualifiedName) error { } // outputPublishedODataServiceMDL outputs a published OData service in MDL format. -func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataService, moduleName string, folderPath string) error { +func outputPublishedODataServiceMDL(ctx *ExecContext, svc *model.PublishedODataService, moduleName string, folderPath string) error { // Use Description for javadoc (the user-visible API description) if svc.Description != "" { - outputJavadoc(e.output, svc.Description) + outputJavadoc(ctx.Output, svc.Description) } - fmt.Fprintf(e.output, "CREATE ODATA SERVICE %s.%s (\n", moduleName, svc.Name) + fmt.Fprintf(ctx.Output, "CREATE ODATA SERVICE %s.%s (\n", moduleName, svc.Name) var props []string if folderPath != "" { @@ -332,21 +341,21 @@ func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataServi if svc.PublishAssociations { props = append(props, " PublishAssociations: Yes") } - fmt.Fprintln(e.output, strings.Join(props, ",\n")) + fmt.Fprintln(ctx.Output, strings.Join(props, ",\n")) - fmt.Fprintln(e.output, ")") + fmt.Fprintln(ctx.Output, ")") // Authentication types if len(svc.AuthenticationTypes) > 0 { - fmt.Fprintf(e.output, "AUTHENTICATION %s\n", strings.Join(svc.AuthenticationTypes, ", ")) + fmt.Fprintf(ctx.Output, "AUTHENTICATION %s\n", strings.Join(svc.AuthenticationTypes, ", ")) } if svc.AuthMicroflow != "" { - fmt.Fprintf(e.output, "-- Auth Microflow: %s\n", svc.AuthMicroflow) + fmt.Fprintf(ctx.Output, "-- Auth Microflow: %s\n", svc.AuthMicroflow) } // Published entities block if len(svc.EntityTypes) > 0 || len(svc.EntitySets) > 0 { - fmt.Fprintln(e.output, "{") + fmt.Fprintln(ctx.Output, "{") // Build entity set lookup by exposed name and entity type name for merging entitySetByExposedName := make(map[string]*model.PublishedEntitySet) @@ -371,7 +380,7 @@ func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataServi doc = et.Description } } - outputJavadocIndented(e.output, doc, " ") + outputJavadocIndented(ctx.Output, doc, " ") } // Find matching entity set (try exposed name first, then entity reference) @@ -381,7 +390,7 @@ func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataServi } // PUBLISH ENTITY line with modes - fmt.Fprintf(e.output, " PUBLISH ENTITY %s AS '%s'", et.Entity, et.ExposedName) + fmt.Fprintf(ctx.Output, " PUBLISH ENTITY %s AS '%s'", et.Entity, et.ExposedName) if es != nil { var modeProps []string if es.ReadMode != "" { @@ -401,14 +410,14 @@ func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataServi modeProps = append(modeProps, fmt.Sprintf("PageSize: %d", es.PageSize)) } if len(modeProps) > 0 { - fmt.Fprintf(e.output, " (\n %s\n )", strings.Join(modeProps, ",\n ")) + fmt.Fprintf(ctx.Output, " (\n %s\n )", strings.Join(modeProps, ",\n ")) } } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // EXPOSE members if len(et.Members) > 0 { - fmt.Fprintln(e.output, " EXPOSE (") + fmt.Fprintln(ctx.Output, " EXPOSE (") for i, m := range et.Members { var modifiers []string if m.Filterable { @@ -428,30 +437,32 @@ func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataServi if i < len(et.Members)-1 { line += "," } - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } - fmt.Fprintln(e.output, " );") + fmt.Fprintln(ctx.Output, " );") } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, "}") + fmt.Fprintln(ctx.Output, "}") } // Output GRANT statements for allowed module roles if len(svc.AllowedModuleRoles) > 0 { - fmt.Fprintln(e.output) - fmt.Fprintf(e.output, "GRANT ACCESS ON ODATA SERVICE %s.%s TO %s;\n", + fmt.Fprintln(ctx.Output) + fmt.Fprintf(ctx.Output, "GRANT ACCESS ON ODATA SERVICE %s.%s TO %s;\n", moduleName, svc.Name, strings.Join(svc.AllowedModuleRoles, ", ")) } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } // showExternalEntities handles SHOW EXTERNAL ENTITIES [IN module] command. -func (e *Executor) showExternalEntities(moduleName string) error { +func showExternalEntities(ctx *ExecContext, moduleName string) error { + e := ctx.executor + domainModels, err := e.reader.ListDomainModels() if err != nil { return mdlerrors.NewBackend("list domain models", err) @@ -495,7 +506,7 @@ func (e *Executor) showExternalEntities(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No external entities found.") + fmt.Fprintln(ctx.Output, "No external entities found.") return nil } @@ -517,7 +528,9 @@ func (e *Executor) showExternalEntities(moduleName string) error { // showExternalActions handles SHOW EXTERNAL ACTIONS [IN module] command. // It scans all microflows and nanoflows for CallExternalAction activities // and displays the unique actions grouped by consumed OData service. -func (e *Executor) showExternalActions(moduleName string) error { +func showExternalActions(ctx *ExecContext, moduleName string) error { + e := ctx.executor + mfs, err := e.reader.ListMicroflows() if err != nil { return mdlerrors.NewBackend("list microflows", err) @@ -610,7 +623,7 @@ func (e *Executor) showExternalActions(moduleName string) error { } if len(actionMap) == 0 { - fmt.Fprintln(e.output, "No external actions found.") + fmt.Fprintln(ctx.Output, "No external actions found.") return nil } @@ -648,7 +661,9 @@ func (e *Executor) showExternalActions(moduleName string) error { } // describeExternalEntity handles DESCRIBE EXTERNAL ENTITY command. -func (e *Executor) describeExternalEntity(name ast.QualifiedName) error { +func describeExternalEntity(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + domainModels, err := e.reader.ListDomainModels() if err != nil { return mdlerrors.NewBackend("list domain models", err) @@ -675,7 +690,7 @@ func (e *Executor) describeExternalEntity(name ast.QualifiedName) error { return mdlerrors.NewValidationf("%s.%s is not an external entity (source: %s)", modName, entity.Name, entity.Source) } - return e.outputExternalEntityMDL(entity, modName) + return outputExternalEntityMDL(ctx, entity, modName) } } @@ -683,14 +698,14 @@ func (e *Executor) describeExternalEntity(name ast.QualifiedName) error { } // outputExternalEntityMDL outputs an external entity in MDL format. -func (e *Executor) outputExternalEntityMDL(entity *domainmodel.Entity, moduleName string) error { +func outputExternalEntityMDL(ctx *ExecContext, entity *domainmodel.Entity, moduleName string) error { if entity.Documentation != "" { - outputJavadoc(e.output, entity.Documentation) + outputJavadoc(ctx.Output, entity.Documentation) } - fmt.Fprintf(e.output, "CREATE EXTERNAL ENTITY %s.%s\n", moduleName, entity.Name) - fmt.Fprintf(e.output, "FROM ODATA CLIENT %s\n", entity.RemoteServiceName) - fmt.Fprintln(e.output, "(") + fmt.Fprintf(ctx.Output, "CREATE EXTERNAL ENTITY %s.%s\n", moduleName, entity.Name) + fmt.Fprintf(ctx.Output, "FROM ODATA CLIENT %s\n", entity.RemoteServiceName) + fmt.Fprintln(ctx.Output, "(") var props []string if entity.RemoteEntitySet != "" { @@ -709,13 +724,13 @@ func (e *Executor) outputExternalEntityMDL(entity *domainmodel.Entity, moduleNam props = append(props, fmt.Sprintf(" Creatable: %s", boolStr(entity.Creatable))) props = append(props, fmt.Sprintf(" Deletable: %s", boolStr(entity.Deletable))) props = append(props, fmt.Sprintf(" Updatable: %s", boolStr(entity.Updatable))) - fmt.Fprintln(e.output, strings.Join(props, ",\n")) + fmt.Fprintln(ctx.Output, strings.Join(props, ",\n")) - fmt.Fprintln(e.output, ")") + fmt.Fprintln(ctx.Output, ")") // Output attributes if len(entity.Attributes) > 0 { - fmt.Fprintln(e.output, "(") + fmt.Fprintln(ctx.Output, "(") for i, attr := range entity.Attributes { typeName := "Unknown" if attr.Type != nil { @@ -725,12 +740,12 @@ func (e *Executor) outputExternalEntityMDL(entity *domainmodel.Entity, moduleNam if i == len(entity.Attributes)-1 { comma = "" } - fmt.Fprintf(e.output, " %s: %s%s\n", attr.Name, typeName, comma) + fmt.Fprintf(ctx.Output, " %s: %s%s\n", attr.Name, typeName, comma) } - fmt.Fprintln(e.output, ");") + fmt.Fprintln(ctx.Output, ");") } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } @@ -740,7 +755,9 @@ func (e *Executor) outputExternalEntityMDL(entity *domainmodel.Entity, moduleNam // ============================================================================ // execCreateExternalEntity handles CREATE [OR MODIFY] EXTERNAL ENTITY statements. -func (e *Executor) execCreateExternalEntity(s *ast.CreateExternalEntityStmt) error { +func execCreateExternalEntity(ctx *ExecContext, s *ast.CreateExternalEntityStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -807,7 +824,7 @@ func (e *Executor) execCreateExternalEntity(s *ast.CreateExternalEntityStmt) err if err := e.writer.UpdateEntity(dm.ID, existingEntity); err != nil { return mdlerrors.NewBackend("update external entity", err) } - fmt.Fprintf(e.output, "Modified external entity: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Modified external entity: %s.%s\n", s.Name.Module, s.Name.Name) return nil } @@ -834,7 +851,7 @@ func (e *Executor) execCreateExternalEntity(s *ast.CreateExternalEntityStmt) err if err := e.writer.CreateEntity(dm.ID, newEntity); err != nil { return mdlerrors.NewBackend("create external entity", err) } - fmt.Fprintf(e.output, "Created external entity: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created external entity: %s.%s\n", s.Name.Module, s.Name.Name) return nil } @@ -843,7 +860,9 @@ func (e *Executor) execCreateExternalEntity(s *ast.CreateExternalEntityStmt) err // ============================================================================ // createODataClient handles CREATE ODATA CLIENT command. -func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { +func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -937,7 +956,7 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { return mdlerrors.NewBackend("update OData client", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified OData client: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Modified OData client: %s.%s\n", modName, svc.Name) return nil } return mdlerrors.NewAlreadyExistsMsg("OData client", modName+"."+svc.Name, fmt.Sprintf("OData client already exists: %s.%s (use CREATE OR MODIFY to update)", modName, svc.Name)) @@ -1005,7 +1024,7 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { if newSvc.MetadataUrl != "" { metadata, hash, err := fetchODataMetadata(newSvc.MetadataUrl) if err != nil { - fmt.Fprintf(e.output, "Warning: could not fetch $metadata: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: could not fetch $metadata: %v\n", err) } else if metadata != "" { newSvc.Metadata = metadata newSvc.MetadataHash = hash @@ -1017,7 +1036,7 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { return mdlerrors.NewBackend("create OData client", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created OData client: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + fmt.Fprintf(ctx.Output, "Created OData client: %s.%s\n", stmt.Name.Module, stmt.Name.Name) if newSvc.Metadata != "" { // Parse to show summary if doc, err := mpr.ParseEdmx(newSvc.Metadata); err == nil { @@ -1027,14 +1046,16 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { entityCount += len(s.EntityTypes) } actionCount = len(doc.Actions) - fmt.Fprintf(e.output, " Cached $metadata: %d entity types, %d actions\n", entityCount, actionCount) + fmt.Fprintf(ctx.Output, " Cached $metadata: %d entity types, %d actions\n", entityCount, actionCount) } } return nil } // alterODataClient handles ALTER ODATA CLIENT command. -func (e *Executor) alterODataClient(stmt *ast.AlterODataClientStmt) error { +func alterODataClient(ctx *ExecContext, stmt *ast.AlterODataClientStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1114,7 +1135,7 @@ func (e *Executor) alterODataClient(stmt *ast.AlterODataClientStmt) error { return mdlerrors.NewBackend("alter OData client", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered OData client: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Altered OData client: %s.%s\n", modName, svc.Name) return nil } } @@ -1123,7 +1144,9 @@ func (e *Executor) alterODataClient(stmt *ast.AlterODataClientStmt) error { } // dropODataClient handles DROP ODATA CLIENT command. -func (e *Executor) dropODataClient(stmt *ast.DropODataClientStmt) error { +func dropODataClient(ctx *ExecContext, stmt *ast.DropODataClientStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1146,7 +1169,7 @@ func (e *Executor) dropODataClient(stmt *ast.DropODataClientStmt) error { return mdlerrors.NewBackend("drop OData client", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped OData client: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Dropped OData client: %s.%s\n", modName, svc.Name) return nil } } @@ -1155,7 +1178,9 @@ func (e *Executor) dropODataClient(stmt *ast.DropODataClientStmt) error { } // createODataService handles CREATE ODATA SERVICE command. -func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { +func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1208,7 +1233,7 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { return mdlerrors.NewBackend("update OData service", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified OData service: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Modified OData service: %s.%s\n", modName, svc.Name) return nil } return mdlerrors.NewAlreadyExistsMsg("OData service", modName+"."+svc.Name, fmt.Sprintf("OData service already exists: %s.%s (use CREATE OR MODIFY to update)", modName, svc.Name)) @@ -1252,12 +1277,14 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { return mdlerrors.NewBackend("create OData service", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created OData service: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + fmt.Fprintf(ctx.Output, "Created OData service: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } // alterODataService handles ALTER ODATA SERVICE command. -func (e *Executor) alterODataService(stmt *ast.AlterODataServiceStmt) error { +func alterODataService(ctx *ExecContext, stmt *ast.AlterODataServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1303,7 +1330,7 @@ func (e *Executor) alterODataService(stmt *ast.AlterODataServiceStmt) error { return mdlerrors.NewBackend("alter OData service", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered OData service: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Altered OData service: %s.%s\n", modName, svc.Name) return nil } } @@ -1312,7 +1339,9 @@ func (e *Executor) alterODataService(stmt *ast.AlterODataServiceStmt) error { } // dropODataService handles DROP ODATA SERVICE command. -func (e *Executor) dropODataService(stmt *ast.DropODataServiceStmt) error { +func dropODataService(ctx *ExecContext, stmt *ast.DropODataServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -1335,7 +1364,7 @@ func (e *Executor) dropODataService(stmt *ast.DropODataServiceStmt) error { return mdlerrors.NewBackend("drop OData service", err) } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped OData service: %s.%s\n", modName, svc.Name) + fmt.Fprintf(ctx.Output, "Dropped OData service: %s.%s\n", modName, svc.Name) return nil } } @@ -1433,3 +1462,41 @@ func fetchODataMetadata(metadataUrl string) (metadata string, hash string, err e hash = fmt.Sprintf("%x", h) return metadata, hash, nil } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) showODataClients(moduleName string) error { + return showODataClients(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeODataClient(name ast.QualifiedName) error { + return describeODataClient(e.newExecContext(context.Background()), name) +} + +func (e *Executor) outputConsumedODataServiceMDL(svc *model.ConsumedODataService, moduleName string, folderPath string) error { + return outputConsumedODataServiceMDL(e.newExecContext(context.Background()), svc, moduleName, folderPath) +} + +func (e *Executor) showODataServices(moduleName string) error { + return showODataServices(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeODataService(name ast.QualifiedName) error { + return describeODataService(e.newExecContext(context.Background()), name) +} + +func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataService, moduleName string, folderPath string) error { + return outputPublishedODataServiceMDL(e.newExecContext(context.Background()), svc, moduleName, folderPath) +} + +func (e *Executor) showExternalEntities(moduleName string) error { + return showExternalEntities(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) showExternalActions(moduleName string) error { + return showExternalActions(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeExternalEntity(name ast.QualifiedName) error { + return describeExternalEntity(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_published_rest.go b/mdl/executor/cmd_published_rest.go index eb52985e..f0b57078 100644 --- a/mdl/executor/cmd_published_rest.go +++ b/mdl/executor/cmd_published_rest.go @@ -3,6 +3,7 @@ package executor import ( + "context" "errors" "fmt" "sort" @@ -14,7 +15,9 @@ import ( ) // showPublishedRestServices handles SHOW PUBLISHED REST SERVICES [IN module] command. -func (e *Executor) showPublishedRestServices(moduleName string) error { +func showPublishedRestServices(ctx *ExecContext, moduleName string) error { + e := ctx.executor + services, err := e.reader.ListPublishedRestServices() if err != nil { return mdlerrors.NewBackend("list published REST services", err) @@ -57,7 +60,7 @@ func (e *Executor) showPublishedRestServices(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No published REST services found.") + fmt.Fprintln(ctx.Output, "No published REST services found.") return nil } @@ -76,7 +79,9 @@ func (e *Executor) showPublishedRestServices(moduleName string) error { } // describePublishedRestService handles DESCRIBE PUBLISHED REST SERVICE command. -func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { +func describePublishedRestService(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + services, err := e.reader.ListPublishedRestServices() if err != nil { return mdlerrors.NewBackend("list published REST services", err) @@ -97,24 +102,24 @@ func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { } // Output as re-executable MDL - fmt.Fprintf(e.output, "CREATE PUBLISHED REST SERVICE %s (\n", qualifiedName) - fmt.Fprintf(e.output, " Path: '%s'", svc.Path) + fmt.Fprintf(ctx.Output, "CREATE PUBLISHED REST SERVICE %s (\n", qualifiedName) + fmt.Fprintf(ctx.Output, " Path: '%s'", svc.Path) if svc.Version != "" { - fmt.Fprintf(e.output, ",\n Version: '%s'", svc.Version) + fmt.Fprintf(ctx.Output, ",\n Version: '%s'", svc.Version) } if svc.ServiceName != "" { - fmt.Fprintf(e.output, ",\n ServiceName: '%s'", svc.ServiceName) + fmt.Fprintf(ctx.Output, ",\n ServiceName: '%s'", svc.ServiceName) } folderPath := h.BuildFolderPath(svc.ContainerID) if folderPath != "" { - fmt.Fprintf(e.output, ",\n Folder: '%s'", folderPath) + fmt.Fprintf(ctx.Output, ",\n Folder: '%s'", folderPath) } - fmt.Fprintln(e.output, "\n)") + fmt.Fprintln(ctx.Output, "\n)") if len(svc.Resources) > 0 { - fmt.Fprintln(e.output, "{") + fmt.Fprintln(ctx.Output, "{") for _, res := range svc.Resources { - fmt.Fprintf(e.output, " RESOURCE '%s' {\n", res.Name) + fmt.Fprintf(ctx.Output, " RESOURCE '%s' {\n", res.Name) for _, op := range res.Operations { deprecated := "" if op.Deprecated { @@ -132,20 +137,20 @@ func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { if op.Path != "" { opPath = fmt.Sprintf(" '%s'", op.Path) } - fmt.Fprintf(e.output, " %s%s%s%s;%s\n", + fmt.Fprintf(ctx.Output, " %s%s%s%s;%s\n", strings.ToUpper(op.HTTPMethod), opPath, mf, deprecated, summary) } - fmt.Fprintln(e.output, " }") + fmt.Fprintln(ctx.Output, " }") } - fmt.Fprintln(e.output, "};") + fmt.Fprintln(ctx.Output, "};") } else { - fmt.Fprintln(e.output, ";") + fmt.Fprintln(ctx.Output, ";") } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") // Emit GRANT statements for any module roles with access. if len(svc.AllowedRoles) > 0 { - fmt.Fprintf(e.output, "\nGRANT ACCESS ON PUBLISHED REST SERVICE %s.%s TO %s;\n", + fmt.Fprintf(ctx.Output, "\nGRANT ACCESS ON PUBLISHED REST SERVICE %s.%s TO %s;\n", modName, svc.Name, strings.Join(svc.AllowedRoles, ", ")) } @@ -156,7 +161,9 @@ func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { } // findPublishedRestService looks up a published REST service by module and name. -func (e *Executor) findPublishedRestService(moduleName, name string) (*model.PublishedRestService, error) { +func findPublishedRestService(ctx *ExecContext, moduleName, name string) (*model.PublishedRestService, error) { + e := ctx.executor + services, err := e.reader.ListPublishedRestServices() if err != nil { return nil, err @@ -176,7 +183,9 @@ func (e *Executor) findPublishedRestService(moduleName, name string) (*model.Pub } // execCreatePublishedRestService creates a new published REST service. -func (e *Executor) execCreatePublishedRestService(s *ast.CreatePublishedRestServiceStmt) error { +func execCreatePublishedRestService(ctx *ExecContext, s *ast.CreatePublishedRestServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -189,7 +198,7 @@ func (e *Executor) execCreatePublishedRestService(s *ast.CreatePublishedRestServ // Handle CREATE OR REPLACE — delete existing if found if s.CreateOrReplace { - existing, findErr := e.findPublishedRestService(s.Name.Module, s.Name.Name) + existing, findErr := findPublishedRestService(ctx, s.Name.Module, s.Name.Name) var nfe *mdlerrors.NotFoundError if findErr != nil && !errors.As(findErr, &nfe) { return mdlerrors.NewBackend("find existing service", findErr) @@ -245,13 +254,15 @@ func (e *Executor) execCreatePublishedRestService(s *ast.CreatePublishedRestServ } if !e.quiet { - fmt.Fprintf(e.output, "Created published REST service %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created published REST service %s.%s\n", s.Name.Module, s.Name.Name) } return nil } // execDropPublishedRestService deletes a published REST service. -func (e *Executor) execDropPublishedRestService(s *ast.DropPublishedRestServiceStmt) error { +func execDropPublishedRestService(ctx *ExecContext, s *ast.DropPublishedRestServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -274,7 +285,7 @@ func (e *Executor) execDropPublishedRestService(s *ast.DropPublishedRestServiceS return mdlerrors.NewBackend("drop published REST service", err) } if !e.quiet { - fmt.Fprintf(e.output, "Dropped published REST service %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped published REST service %s.%s\n", s.Name.Module, s.Name.Name) } return nil } @@ -300,7 +311,9 @@ func astResourceDefToModel(def *ast.PublishedRestResourceDef) *model.PublishedRe // execAlterPublishedRestService applies SET / ADD RESOURCE / DROP RESOURCE // actions to an existing published REST service. -func (e *Executor) execAlterPublishedRestService(s *ast.AlterPublishedRestServiceStmt) error { +func execAlterPublishedRestService(ctx *ExecContext, s *ast.AlterPublishedRestServiceStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -311,7 +324,7 @@ func (e *Executor) execAlterPublishedRestService(s *ast.AlterPublishedRestServic return err } - svc, err := e.findPublishedRestService(s.Name.Module, s.Name.Name) + svc, err := findPublishedRestService(ctx, s.Name.Module, s.Name.Name) if err != nil { return err } @@ -364,7 +377,17 @@ func (e *Executor) execAlterPublishedRestService(s *ast.AlterPublishedRestServic } if !e.quiet { - fmt.Fprintf(e.output, "Altered published REST service %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Altered published REST service %s.%s\n", s.Name.Module, s.Name.Name) } return nil } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) showPublishedRestServices(moduleName string) error { + return showPublishedRestServices(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { + return describePublishedRestService(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_rest_clients.go b/mdl/executor/cmd_rest_clients.go index c4b304a5..576d4c2a 100644 --- a/mdl/executor/cmd_rest_clients.go +++ b/mdl/executor/cmd_rest_clients.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "io" "sort" @@ -22,7 +23,9 @@ func safeIdent(name string) string { } // showRestClients handles SHOW REST CLIENTS [IN module] command. -func (e *Executor) showRestClients(moduleName string) error { +func showRestClients(ctx *ExecContext, moduleName string) error { + e := ctx.executor + services, err := e.reader.ListConsumedRestServices() if err != nil { return mdlerrors.NewBackend("list consumed REST services", err) @@ -64,7 +67,7 @@ func (e *Executor) showRestClients(moduleName string) error { } if len(rows) == 0 { - fmt.Fprintln(e.output, "No consumed REST services found.") + fmt.Fprintln(ctx.Output, "No consumed REST services found.") return nil } @@ -83,7 +86,9 @@ func (e *Executor) showRestClients(moduleName string) error { } // describeRestClient handles DESCRIBE REST CLIENT command. -func (e *Executor) describeRestClient(name ast.QualifiedName) error { +func describeRestClient(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + services, err := e.reader.ListConsumedRestServices() if err != nil { return mdlerrors.NewBackend("list consumed REST services", err) @@ -98,7 +103,7 @@ func (e *Executor) describeRestClient(name ast.QualifiedName) error { modID := h.FindModuleID(svc.ContainerID) modName := h.GetModuleName(modID) if strings.EqualFold(modName, name.Module) && strings.EqualFold(svc.Name, name.Name) { - return e.outputConsumedRestServiceMDL(svc, modName) + return outputConsumedRestServiceMDL(ctx, svc, modName) } } @@ -106,8 +111,8 @@ func (e *Executor) describeRestClient(name ast.QualifiedName) error { } // outputConsumedRestServiceMDL outputs a consumed REST service in the property-based { } format. -func (e *Executor) outputConsumedRestServiceMDL(svc *model.ConsumedRestService, moduleName string) error { - w := e.output +func outputConsumedRestServiceMDL(ctx *ExecContext, svc *model.ConsumedRestService, moduleName string) error { + w := ctx.Output if svc.Documentation != "" { outputJavadoc(w, svc.Documentation) @@ -276,7 +281,9 @@ func writeExportMappings(w io.Writer, mappings []*model.RestResponseMapping, ind } // createRestClient handles CREATE REST CLIENT statement. -func (e *Executor) createRestClient(stmt *ast.CreateRestClientStmt) error { +func createRestClient(ctx *ExecContext, stmt *ast.CreateRestClientStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -354,7 +361,7 @@ func (e *Executor) createRestClient(stmt *ast.CreateRestClientStmt) error { return mdlerrors.NewBackend("create REST client", err) } - fmt.Fprintf(e.output, "Created REST client: %s.%s (%d operations)\n", moduleName, stmt.Name.Name, len(svc.Operations)) + fmt.Fprintf(ctx.Output, "Created REST client: %s.%s (%d operations)\n", moduleName, stmt.Name.Name, len(svc.Operations)) return nil } @@ -455,7 +462,9 @@ func convertMappingEntries(entries []ast.RestMappingEntry, importDirection bool) } // dropRestClient handles DROP REST CLIENT statement. -func (e *Executor) dropRestClient(stmt *ast.DropRestClientStmt) error { +func dropRestClient(ctx *ExecContext, stmt *ast.DropRestClientStmt) error { + e := ctx.executor + if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -477,7 +486,7 @@ func (e *Executor) dropRestClient(stmt *ast.DropRestClientStmt) error { if err := e.writer.DeleteConsumedRestService(svc.ID); err != nil { return mdlerrors.NewBackend("delete REST client", err) } - fmt.Fprintf(e.output, "Dropped REST client: %s.%s\n", moduleName, svc.Name) + fmt.Fprintf(ctx.Output, "Dropped REST client: %s.%s\n", moduleName, svc.Name) return nil } } @@ -492,3 +501,13 @@ func formatRestAuthValue(value string) string { } return "'" + value + "'" } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) showRestClients(moduleName string) error { + return showRestClients(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeRestClient(name ast.QualifiedName) error { + return describeRestClient(e.newExecContext(context.Background()), name) +} From 29e37c188479b38ee6d229dfa0ec1bafff4b0800 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 16:46:43 +0200 Subject: [PATCH 11/16] refactor: migrate mapping, JSON structure, and Java action handlers to free functions --- mdl/executor/cmd_export_mappings.go | 76 ++++++++++++++++---------- mdl/executor/cmd_import_mappings.go | 74 +++++++++++++++---------- mdl/executor/cmd_javaactions.go | 41 +++++++++----- mdl/executor/cmd_javascript_actions.go | 37 +++++++++---- mdl/executor/cmd_jsonstructures.go | 56 ++++++++++++------- mdl/executor/register_stubs.go | 16 +++--- 6 files changed, 188 insertions(+), 112 deletions(-) diff --git a/mdl/executor/cmd_export_mappings.go b/mdl/executor/cmd_export_mappings.go index 9f2958e5..ca1eb09b 100644 --- a/mdl/executor/cmd_export_mappings.go +++ b/mdl/executor/cmd_export_mappings.go @@ -3,7 +3,9 @@ package executor import ( + "context" "fmt" + "io" "sort" "strings" @@ -14,7 +16,8 @@ import ( ) // showExportMappings prints a table of all export mapping documents. -func (e *Executor) showExportMappings(inModule string) error { +func showExportMappings(ctx *ExecContext, inModule string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -57,9 +60,9 @@ func (e *Executor) showExportMappings(inModule string) error { if len(rows) == 0 { if inModule != "" { - fmt.Fprintf(e.output, "No export mappings found in module %s\n", inModule) + fmt.Fprintf(ctx.Output, "No export mappings found in module %s\n", inModule) } else { - fmt.Fprintln(e.output, "No export mappings found") + fmt.Fprintln(ctx.Output, "No export mappings found") } return nil } @@ -76,8 +79,14 @@ func (e *Executor) showExportMappings(inModule string) error { return e.writeResult(result) } +// showExportMappings is a wrapper for callers that still use an Executor receiver. +func (e *Executor) showExportMappings(inModule string) error { + return showExportMappings(e.newExecContext(context.Background()), inModule) +} + // describeExportMapping prints the MDL representation of an export mapping. -func (e *Executor) describeExportMapping(name ast.QualifiedName) error { +func describeExportMapping(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -91,7 +100,7 @@ func (e *Executor) describeExportMapping(name ast.QualifiedName) error { } if em.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", strings.ReplaceAll(em.Documentation, "\n", "\n * ")) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", strings.ReplaceAll(em.Documentation, "\n", "\n * ")) } h, err := e.getHierarchy() @@ -101,30 +110,35 @@ func (e *Executor) describeExportMapping(name ast.QualifiedName) error { modID := h.FindModuleID(em.ContainerID) moduleName := h.GetModuleName(modID) - fmt.Fprintf(e.output, "CREATE EXPORT MAPPING %s.%s\n", moduleName, em.Name) + fmt.Fprintf(ctx.Output, "CREATE EXPORT MAPPING %s.%s\n", moduleName, em.Name) if em.JsonStructure != "" { - fmt.Fprintf(e.output, " WITH JSON STRUCTURE %s\n", em.JsonStructure) + fmt.Fprintf(ctx.Output, " WITH JSON STRUCTURE %s\n", em.JsonStructure) } else if em.XmlSchema != "" { - fmt.Fprintf(e.output, " WITH XML SCHEMA %s\n", em.XmlSchema) + fmt.Fprintf(ctx.Output, " WITH XML SCHEMA %s\n", em.XmlSchema) } if em.NullValueOption != "" && em.NullValueOption != "LeaveOutElement" { - fmt.Fprintf(e.output, " NULL VALUES %s\n", em.NullValueOption) + fmt.Fprintf(ctx.Output, " NULL VALUES %s\n", em.NullValueOption) } if len(em.Elements) > 0 { - fmt.Fprintln(e.output, "{") + fmt.Fprintln(ctx.Output, "{") for _, elem := range em.Elements { - printExportMappingElement(e, elem, 1, true) - fmt.Fprintln(e.output) + printExportMappingElement(ctx.Output, elem, 1, true) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, "};") + fmt.Fprintln(ctx.Output, "};") } return nil } -func printExportMappingElement(e *Executor, elem *model.ExportMappingElement, depth int, isRoot bool) { +// describeExportMapping is a wrapper for callers that still use an Executor receiver. +func (e *Executor) describeExportMapping(name ast.QualifiedName) error { + return describeExportMapping(e.newExecContext(context.Background()), name) +} + +func printExportMappingElement(w io.Writer, elem *model.ExportMappingElement, depth int, isRoot bool) { indent := strings.Repeat(" ", depth) if elem.Kind == "Object" { if isRoot { @@ -133,7 +147,7 @@ func printExportMappingElement(e *Executor, elem *model.ExportMappingElement, de if entity == "" { entity = "." } - fmt.Fprintf(e.output, "%s%s {\n", indent, entity) + fmt.Fprintf(w, "%s%s {\n", indent, entity) } else { // Nested object element. Several cases: // Assoc/Entity AS jsonKey — normal association path @@ -142,26 +156,26 @@ func printExportMappingElement(e *Executor, elem *model.ExportMappingElement, de assoc := elem.Association entity := elem.Entity if assoc == "" && entity == "" { - fmt.Fprintf(e.output, "%s. AS %s", indent, elem.ExposedName) + fmt.Fprintf(w, "%s. AS %s", indent, elem.ExposedName) } else if assoc == "" { - fmt.Fprintf(e.output, "%s./%s AS %s", indent, entity, elem.ExposedName) + fmt.Fprintf(w, "%s./%s AS %s", indent, entity, elem.ExposedName) } else { - fmt.Fprintf(e.output, "%s%s/%s AS %s", indent, assoc, entity, elem.ExposedName) + fmt.Fprintf(w, "%s%s/%s AS %s", indent, assoc, entity, elem.ExposedName) } if len(elem.Children) > 0 { - fmt.Fprintln(e.output, " {") + fmt.Fprintln(w, " {") } } if len(elem.Children) > 0 { for i, child := range elem.Children { - printExportMappingElement(e, child, depth+1, false) + printExportMappingElement(w, child, depth+1, false) if i < len(elem.Children)-1 { - fmt.Fprintln(e.output, ",") + fmt.Fprintln(w, ",") } else { - fmt.Fprintln(e.output) + fmt.Fprintln(w) } } - fmt.Fprintf(e.output, "%s}", indent) + fmt.Fprintf(w, "%s}", indent) } } else { // Value mapping: jsonField = Attr @@ -170,12 +184,13 @@ func printExportMappingElement(e *Executor, elem *model.ExportMappingElement, de if parts := strings.Split(attrName, "."); len(parts) == 3 { attrName = parts[2] } - fmt.Fprintf(e.output, "%s%s = %s", indent, elem.ExposedName, attrName) + fmt.Fprintf(w, "%s%s = %s", indent, elem.ExposedName, attrName) } } // execCreateExportMapping creates a new export mapping. -func (e *Executor) execCreateExportMapping(s *ast.CreateExportMappingStmt) error { +func execCreateExportMapping(ctx *ExecContext, s *ast.CreateExportMappingStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -222,8 +237,8 @@ func (e *Executor) execCreateExportMapping(s *ast.CreateExportMappingStmt) error return mdlerrors.NewBackend("create export mapping", err) } - if !e.quiet { - fmt.Fprintf(e.output, "Created export mapping %s.%s\n", s.Name.Module, s.Name.Name) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "Created export mapping %s.%s\n", s.Name.Module, s.Name.Name) } return nil } @@ -359,7 +374,8 @@ func buildExportMappingElementModel(moduleName string, def *ast.ExportMappingEle } // execDropExportMapping deletes an export mapping. -func (e *Executor) execDropExportMapping(s *ast.DropExportMappingStmt) error { +func execDropExportMapping(ctx *ExecContext, s *ast.DropExportMappingStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -376,8 +392,8 @@ func (e *Executor) execDropExportMapping(s *ast.DropExportMappingStmt) error { return mdlerrors.NewBackend("drop export mapping", err) } - if !e.quiet { - fmt.Fprintf(e.output, "Dropped export mapping %s.%s\n", s.Name.Module, s.Name.Name) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "Dropped export mapping %s.%s\n", s.Name.Module, s.Name.Name) } return nil } diff --git a/mdl/executor/cmd_import_mappings.go b/mdl/executor/cmd_import_mappings.go index c8cb861c..34fa4b5f 100644 --- a/mdl/executor/cmd_import_mappings.go +++ b/mdl/executor/cmd_import_mappings.go @@ -3,7 +3,9 @@ package executor import ( + "context" "fmt" + "io" "sort" "strings" @@ -14,7 +16,8 @@ import ( ) // showImportMappings prints a table of all import mapping documents. -func (e *Executor) showImportMappings(inModule string) error { +func showImportMappings(ctx *ExecContext, inModule string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -57,9 +60,9 @@ func (e *Executor) showImportMappings(inModule string) error { if len(rows) == 0 { if inModule != "" { - fmt.Fprintf(e.output, "No import mappings found in module %s\n", inModule) + fmt.Fprintf(ctx.Output, "No import mappings found in module %s\n", inModule) } else { - fmt.Fprintln(e.output, "No import mappings found") + fmt.Fprintln(ctx.Output, "No import mappings found") } return nil } @@ -76,8 +79,14 @@ func (e *Executor) showImportMappings(inModule string) error { return e.writeResult(result) } +// showImportMappings is a wrapper for callers that still use an Executor receiver. +func (e *Executor) showImportMappings(inModule string) error { + return showImportMappings(e.newExecContext(context.Background()), inModule) +} + // describeImportMapping prints the MDL representation of an import mapping. -func (e *Executor) describeImportMapping(name ast.QualifiedName) error { +func describeImportMapping(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -91,7 +100,7 @@ func (e *Executor) describeImportMapping(name ast.QualifiedName) error { } if im.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", strings.ReplaceAll(im.Documentation, "\n", "\n * ")) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", strings.ReplaceAll(im.Documentation, "\n", "\n * ")) } h, err := e.getHierarchy() @@ -101,25 +110,30 @@ func (e *Executor) describeImportMapping(name ast.QualifiedName) error { modID := h.FindModuleID(im.ContainerID) moduleName := h.GetModuleName(modID) - fmt.Fprintf(e.output, "CREATE IMPORT MAPPING %s.%s\n", moduleName, im.Name) + fmt.Fprintf(ctx.Output, "CREATE IMPORT MAPPING %s.%s\n", moduleName, im.Name) if im.JsonStructure != "" { - fmt.Fprintf(e.output, " WITH JSON STRUCTURE %s\n", im.JsonStructure) + fmt.Fprintf(ctx.Output, " WITH JSON STRUCTURE %s\n", im.JsonStructure) } else if im.XmlSchema != "" { - fmt.Fprintf(e.output, " WITH XML SCHEMA %s\n", im.XmlSchema) + fmt.Fprintf(ctx.Output, " WITH XML SCHEMA %s\n", im.XmlSchema) } if len(im.Elements) > 0 { - fmt.Fprintln(e.output, "{") + fmt.Fprintln(ctx.Output, "{") for _, elem := range im.Elements { - printImportMappingElement(e, elem, 1, true) - fmt.Fprintln(e.output) + printImportMappingElement(ctx.Output, elem, 1, true) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, "};") + fmt.Fprintln(ctx.Output, "};") } return nil } +// describeImportMapping is a wrapper for callers that still use an Executor receiver. +func (e *Executor) describeImportMapping(name ast.QualifiedName) error { + return describeImportMapping(e.newExecContext(context.Background()), name) +} + // handlingKeyword returns the MDL keyword for a Mendix ObjectHandling value. func handlingKeyword(handling string) string { switch handling { @@ -132,7 +146,7 @@ func handlingKeyword(handling string) string { } } -func printImportMappingElement(e *Executor, elem *model.ImportMappingElement, depth int, isRoot bool) { +func printImportMappingElement(w io.Writer, elem *model.ImportMappingElement, depth int, isRoot bool) { indent := strings.Repeat(" ", depth) if elem.Kind == "Object" { handling := handlingKeyword(elem.ObjectHandling) @@ -142,7 +156,7 @@ func printImportMappingElement(e *Executor, elem *model.ImportMappingElement, de if entity == "" { entity = "." } - fmt.Fprintf(e.output, "%s%s %s {\n", indent, handling, entity) + fmt.Fprintf(w, "%s%s %s {\n", indent, handling, entity) } else { // Nested object element: // CREATE Assoc/Entity = jsonKey — normal association path @@ -151,26 +165,26 @@ func printImportMappingElement(e *Executor, elem *model.ImportMappingElement, de assoc := elem.Association entity := elem.Entity if assoc == "" && entity == "" { - fmt.Fprintf(e.output, "%s%s . = %s", indent, handling, elem.ExposedName) + fmt.Fprintf(w, "%s%s . = %s", indent, handling, elem.ExposedName) } else if assoc == "" { - fmt.Fprintf(e.output, "%s%s ./%s = %s", indent, handling, entity, elem.ExposedName) + fmt.Fprintf(w, "%s%s ./%s = %s", indent, handling, entity, elem.ExposedName) } else { - fmt.Fprintf(e.output, "%s%s %s/%s = %s", indent, handling, assoc, entity, elem.ExposedName) + fmt.Fprintf(w, "%s%s %s/%s = %s", indent, handling, assoc, entity, elem.ExposedName) } if len(elem.Children) > 0 { - fmt.Fprintln(e.output, " {") + fmt.Fprintln(w, " {") } } if len(elem.Children) > 0 { for i, child := range elem.Children { - printImportMappingElement(e, child, depth+1, false) + printImportMappingElement(w, child, depth+1, false) if i < len(elem.Children)-1 { - fmt.Fprintln(e.output, ",") + fmt.Fprintln(w, ",") } else { - fmt.Fprintln(e.output) + fmt.Fprintln(w) } } - fmt.Fprintf(e.output, "%s}", indent) + fmt.Fprintf(w, "%s}", indent) } } else { // Value mapping: Attr = jsonField KEY @@ -183,12 +197,13 @@ func printImportMappingElement(e *Executor, elem *model.ImportMappingElement, de if elem.IsKey { keyStr = " KEY" } - fmt.Fprintf(e.output, "%s%s = %s%s", indent, attrName, elem.ExposedName, keyStr) + fmt.Fprintf(w, "%s%s = %s%s", indent, attrName, elem.ExposedName, keyStr) } } // execCreateImportMapping creates a new import mapping. -func (e *Executor) execCreateImportMapping(s *ast.CreateImportMappingStmt) error { +func execCreateImportMapping(ctx *ExecContext, s *ast.CreateImportMappingStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -231,8 +246,8 @@ func (e *Executor) execCreateImportMapping(s *ast.CreateImportMappingStmt) error return mdlerrors.NewBackend("create import mapping", err) } - if !e.quiet { - fmt.Fprintf(e.output, "Created import mapping %s.%s\n", s.Name.Module, s.Name.Name) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "Created import mapping %s.%s\n", s.Name.Module, s.Name.Name) } return nil } @@ -373,7 +388,8 @@ func resolveAttributeType(entityQN, attrName string, reader *mpr.Reader) string } // execDropImportMapping deletes an import mapping. -func (e *Executor) execDropImportMapping(s *ast.DropImportMappingStmt) error { +func execDropImportMapping(ctx *ExecContext, s *ast.DropImportMappingStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -390,8 +406,8 @@ func (e *Executor) execDropImportMapping(s *ast.DropImportMappingStmt) error { return mdlerrors.NewBackend("drop import mapping", err) } - if !e.quiet { - fmt.Fprintf(e.output, "Dropped import mapping %s.%s\n", s.Name.Module, s.Name.Name) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "Dropped import mapping %s.%s\n", s.Name.Module, s.Name.Name) } return nil } diff --git a/mdl/executor/cmd_javaactions.go b/mdl/executor/cmd_javaactions.go index 58411555..450bd652 100644 --- a/mdl/executor/cmd_javaactions.go +++ b/mdl/executor/cmd_javaactions.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "os" "path/filepath" @@ -18,7 +19,8 @@ import ( ) // showJavaActions handles SHOW JAVA ACTIONS command. -func (e *Executor) showJavaActions(moduleName string) error { +func showJavaActions(ctx *ExecContext, moduleName string) error { + e := ctx.executor // Get hierarchy for module/folder resolution h, err := e.getHierarchy() if err != nil { @@ -65,8 +67,14 @@ func (e *Executor) showJavaActions(moduleName string) error { return e.writeResult(result) } +// showJavaActions is a wrapper for callers that still use an Executor receiver. +func (e *Executor) showJavaActions(moduleName string) error { + return showJavaActions(e.newExecContext(context.Background()), moduleName) +} + // describeJavaAction handles DESCRIBE JAVA ACTION command - outputs MDL-style representation. -func (e *Executor) describeJavaAction(name ast.QualifiedName) error { +func describeJavaAction(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor qualifiedName := name.Module + "." + name.Name ja, err := e.reader.ReadJavaActionByName(qualifiedName) if err != nil { @@ -160,7 +168,7 @@ func (e *Executor) describeJavaAction(name ast.QualifiedName) error { } // Try to read and include Java source code - javaCode := e.readJavaActionUserCode(name.Module, name.Name) + javaCode := readJavaActionUserCode(e.mprPath, name.Module, name.Name) if javaCode != "" { sb.WriteString("\nAS $$\n") sb.WriteString(javaCode) @@ -170,27 +178,32 @@ func (e *Executor) describeJavaAction(name ast.QualifiedName) error { sb.WriteString(";") // Output the complete statement - fmt.Fprintln(e.output, sb.String()) + fmt.Fprintln(ctx.Output, sb.String()) // Additional info as comments if ja.ExportLevel != "" && ja.ExportLevel != "Hidden" { - fmt.Fprintf(e.output, "-- EXPORT LEVEL: %s\n", ja.ExportLevel) + fmt.Fprintf(ctx.Output, "-- EXPORT LEVEL: %s\n", ja.ExportLevel) } if ja.Excluded { - fmt.Fprintln(e.output, "-- EXCLUDED: true") + fmt.Fprintln(ctx.Output, "-- EXCLUDED: true") } return nil } +// describeJavaAction is a wrapper for callers that still use an Executor receiver. +func (e *Executor) describeJavaAction(name ast.QualifiedName) error { + return describeJavaAction(e.newExecContext(context.Background()), name) +} + // readJavaActionUserCode reads the Java source file and extracts the user code section. -func (e *Executor) readJavaActionUserCode(moduleName, actionName string) string { - if e.mprPath == "" { +func readJavaActionUserCode(mprPath, moduleName, actionName string) string { + if mprPath == "" { return "" } // Build path to Java source file - projectRoot := filepath.Dir(e.mprPath) + projectRoot := filepath.Dir(mprPath) moduleNameLower := strings.ToLower(moduleName) javaPath := filepath.Join(projectRoot, "javasource", moduleNameLower, "actions", actionName+".java") @@ -246,7 +259,8 @@ func formatJavaActionReturnType(t javaactions.CodeActionReturnType) string { } // execDropJavaAction handles DROP JAVA ACTION statements. -func (e *Executor) execDropJavaAction(s *ast.DropJavaActionStmt) error { +func execDropJavaAction(ctx *ExecContext, s *ast.DropJavaActionStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -270,7 +284,7 @@ func (e *Executor) execDropJavaAction(s *ast.DropJavaActionStmt) error { if err := e.writer.DeleteJavaAction(ja.ID); err != nil { return mdlerrors.NewBackend("delete java action", err) } - fmt.Fprintf(e.output, "Dropped java action: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Dropped java action: %s.%s\n", s.Name.Module, s.Name.Name) return nil } } @@ -279,7 +293,8 @@ func (e *Executor) execDropJavaAction(s *ast.DropJavaActionStmt) error { } // execCreateJavaAction handles CREATE JAVA ACTION statements. -func (e *Executor) execCreateJavaAction(s *ast.CreateJavaActionStmt) error { +func execCreateJavaAction(ctx *ExecContext, s *ast.CreateJavaActionStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } @@ -424,7 +439,7 @@ func (e *Executor) execCreateJavaAction(s *ast.CreateJavaActionStmt) error { // Clear cache e.cache = nil - fmt.Fprintf(e.output, "Created java action: %s.%s\n", s.Name.Module, s.Name.Name) + fmt.Fprintf(ctx.Output, "Created java action: %s.%s\n", s.Name.Module, s.Name.Name) return nil } diff --git a/mdl/executor/cmd_javascript_actions.go b/mdl/executor/cmd_javascript_actions.go index b813437a..5897bd0c 100644 --- a/mdl/executor/cmd_javascript_actions.go +++ b/mdl/executor/cmd_javascript_actions.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "os" "path/filepath" @@ -16,7 +17,8 @@ import ( ) // showJavaScriptActions handles SHOW JAVASCRIPT ACTIONS command. -func (e *Executor) showJavaScriptActions(moduleName string) error { +func showJavaScriptActions(ctx *ExecContext, moduleName string) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -64,8 +66,14 @@ func (e *Executor) showJavaScriptActions(moduleName string) error { return e.writeResult(result) } +// showJavaScriptActions is a wrapper for callers that still use an Executor receiver. +func (e *Executor) showJavaScriptActions(moduleName string) error { + return showJavaScriptActions(e.newExecContext(context.Background()), moduleName) +} + // describeJavaScriptAction handles DESCRIBE JAVASCRIPT ACTION command. -func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { +func describeJavaScriptAction(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor qualifiedName := name.Module + "." + name.Name jsa, err := e.reader.ReadJavaScriptActionByName(qualifiedName) if err != nil { @@ -178,7 +186,7 @@ func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { } // JavaScript source code - userCode, extraCode := e.readJavaScriptActionSource(name.Module, name.Name) + userCode, extraCode := readJavaScriptActionSource(e.mprPath, name.Module, name.Name) if userCode != "" { sb.WriteString("\nAS $$\n") sb.WriteString(userCode) @@ -187,37 +195,42 @@ func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { sb.WriteString(";") - fmt.Fprintln(e.output, sb.String()) + fmt.Fprintln(ctx.Output, sb.String()) // Additional info as comments if jsa.ExportLevel != "" && jsa.ExportLevel != "Hidden" { - fmt.Fprintf(e.output, "-- EXPORT LEVEL: %s\n", jsa.ExportLevel) + fmt.Fprintf(ctx.Output, "-- EXPORT LEVEL: %s\n", jsa.ExportLevel) } if jsa.Excluded { - fmt.Fprintln(e.output, "-- EXCLUDED: true") + fmt.Fprintln(ctx.Output, "-- EXCLUDED: true") } if platform != "All" { // already shown inline } else { - fmt.Fprintln(e.output, "-- PLATFORM: All") + fmt.Fprintln(ctx.Output, "-- PLATFORM: All") } if extraCode != "" { - fmt.Fprintln(e.output, "-- EXTRA CODE:") + fmt.Fprintln(ctx.Output, "-- EXTRA CODE:") for line := range strings.SplitSeq(extraCode, "\n") { - fmt.Fprintf(e.output, "-- %s\n", line) + fmt.Fprintf(ctx.Output, "-- %s\n", line) } } return nil } +// describeJavaScriptAction is a wrapper for callers that still use an Executor receiver. +func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { + return describeJavaScriptAction(e.newExecContext(context.Background()), name) +} + // readJavaScriptActionSource reads the JavaScript source file and extracts user code and extra code. -func (e *Executor) readJavaScriptActionSource(moduleName, actionName string) (userCode, extraCode string) { - if e.mprPath == "" { +func readJavaScriptActionSource(mprPath, moduleName, actionName string) (userCode, extraCode string) { + if mprPath == "" { return "", "" } - projectRoot := filepath.Dir(e.mprPath) + projectRoot := filepath.Dir(mprPath) // JavaScript source uses original module name casing (not lowercased like javasource) jsPath := filepath.Join(projectRoot, "javascriptsource", moduleName, "actions", actionName+".js") diff --git a/mdl/executor/cmd_jsonstructures.go b/mdl/executor/cmd_jsonstructures.go index 4b49ea96..caea614a 100644 --- a/mdl/executor/cmd_jsonstructures.go +++ b/mdl/executor/cmd_jsonstructures.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,7 +16,8 @@ import ( ) // showJsonStructures handles SHOW JSON STRUCTURES [IN module]. -func (e *Executor) showJsonStructures(moduleName string) error { +func showJsonStructures(ctx *ExecContext, moduleName string) error { + e := ctx.executor structures, err := e.reader.ListJsonStructures() if err != nil { return mdlerrors.NewBackend("list JSON structures", err) @@ -68,10 +70,16 @@ func (e *Executor) showJsonStructures(moduleName string) error { return e.writeResult(tr) } +// showJsonStructures is a wrapper for callers that still use an Executor receiver. +func (e *Executor) showJsonStructures(moduleName string) error { + return showJsonStructures(e.newExecContext(context.Background()), moduleName) +} + // describeJsonStructure handles DESCRIBE JSON STRUCTURE Module.Name. // Output is re-executable CREATE OR REPLACE MDL followed by the element tree as comments. -func (e *Executor) describeJsonStructure(name ast.QualifiedName) error { - js := e.findJsonStructure(name.Module, name.Name) +func describeJsonStructure(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + js := findJsonStructure(ctx, name.Module, name.Name) if js == nil { return mdlerrors.NewNotFound("JSON structure", name.String()) } @@ -87,24 +95,24 @@ func (e *Executor) describeJsonStructure(name ast.QualifiedName) error { // Documentation as doc comment if js.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", js.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", js.Documentation) } // Re-executable CREATE OR REPLACE statement - fmt.Fprintf(e.output, "CREATE OR REPLACE JSON STRUCTURE %s", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE OR REPLACE JSON STRUCTURE %s", qualifiedName) if folderPath := h.BuildFolderPath(js.ContainerID); folderPath != "" { - fmt.Fprintf(e.output, "\n FOLDER '%s'", folderPath) + fmt.Fprintf(ctx.Output, "\n FOLDER '%s'", folderPath) } if js.Documentation != "" { - fmt.Fprintf(e.output, "\n COMMENT '%s'", strings.ReplaceAll(js.Documentation, "'", "''")) + fmt.Fprintf(ctx.Output, "\n COMMENT '%s'", strings.ReplaceAll(js.Documentation, "'", "''")) } if js.JsonSnippet != "" { snippet := mpr.PrettyPrintJSON(js.JsonSnippet) if strings.Contains(snippet, "'") || strings.Contains(snippet, "\n") { - fmt.Fprintf(e.output, "\n SNIPPET $$%s$$", snippet) + fmt.Fprintf(ctx.Output, "\n SNIPPET $$%s$$", snippet) } else { - fmt.Fprintf(e.output, "\n SNIPPET '%s'", snippet) + fmt.Fprintf(ctx.Output, "\n SNIPPET '%s'", snippet) } } @@ -118,21 +126,26 @@ func (e *Executor) describeJsonStructure(name ast.QualifiedName) error { } sort.Strings(keys) - fmt.Fprintf(e.output, "\n CUSTOM NAME MAP (\n") + fmt.Fprintf(ctx.Output, "\n CUSTOM NAME MAP (\n") for i, jsonKey := range keys { sep := "," if i == len(keys)-1 { sep = "" } - fmt.Fprintf(e.output, " '%s' AS '%s'%s\n", jsonKey, customMappings[jsonKey], sep) + fmt.Fprintf(ctx.Output, " '%s' AS '%s'%s\n", jsonKey, customMappings[jsonKey], sep) } - fmt.Fprintf(e.output, " )") + fmt.Fprintf(ctx.Output, " )") } - fmt.Fprintln(e.output, ";") + fmt.Fprintln(ctx.Output, ";") return nil } +// describeJsonStructure is a wrapper for callers that still use an Executor receiver. +func (e *Executor) describeJsonStructure(name ast.QualifiedName) error { + return describeJsonStructure(e.newExecContext(context.Background()), name) +} + // collectCustomNameMappings walks the element tree and returns JSON key → ExposedName // mappings where the ExposedName differs from the auto-generated default (capitalizeFirst). func collectCustomNameMappings(elements []*mpr.JsonElement) map[string]string { @@ -172,7 +185,8 @@ func capitalizeFirstRune(s string) string { } // execCreateJsonStructure handles CREATE [OR REPLACE] JSON STRUCTURE statements. -func (e *Executor) execCreateJsonStructure(s *ast.CreateJsonStructureStmt) error { +func execCreateJsonStructure(ctx *ExecContext, s *ast.CreateJsonStructureStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -194,7 +208,7 @@ func (e *Executor) execCreateJsonStructure(s *ast.CreateJsonStructureStmt) error } // Check if already exists - existing := e.findJsonStructure(s.Name.Module, s.Name.Name) + existing := findJsonStructure(ctx, s.Name.Module, s.Name.Name) if existing != nil { if s.CreateOrReplace { // Delete existing before recreating @@ -236,17 +250,18 @@ func (e *Executor) execCreateJsonStructure(s *ast.CreateJsonStructureStmt) error if existing != nil { action = "Replaced" } - fmt.Fprintf(e.output, "%s JSON structure: %s\n", action, s.Name) + fmt.Fprintf(ctx.Output, "%s JSON structure: %s\n", action, s.Name) return nil } // execDropJsonStructure handles DROP JSON STRUCTURE statements. -func (e *Executor) execDropJsonStructure(s *ast.DropJsonStructureStmt) error { +func execDropJsonStructure(ctx *ExecContext, s *ast.DropJsonStructureStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } - js := e.findJsonStructure(s.Name.Module, s.Name.Name) + js := findJsonStructure(ctx, s.Name.Module, s.Name.Name) if js == nil { return mdlerrors.NewNotFound("JSON structure", s.Name.String()) } @@ -255,12 +270,13 @@ func (e *Executor) execDropJsonStructure(s *ast.DropJsonStructureStmt) error { return mdlerrors.NewBackend("delete JSON structure", err) } - fmt.Fprintf(e.output, "Dropped JSON structure: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped JSON structure: %s\n", s.Name) return nil } // findJsonStructure finds a JSON structure by module and name. -func (e *Executor) findJsonStructure(moduleName, structName string) *mpr.JsonStructure { +func findJsonStructure(ctx *ExecContext, moduleName, structName string) *mpr.JsonStructure { + e := ctx.executor structures, err := e.reader.ListJsonStructures() if err != nil { return nil diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 81e36f10..3863748b 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -107,10 +107,10 @@ func registerPageHandlers(r *Registry) { return execDropSnippet(ctx, stmt.(*ast.DropSnippetStmt)) }) r.Register(&ast.DropJavaActionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropJavaAction(stmt.(*ast.DropJavaActionStmt)) + return execDropJavaAction(ctx, stmt.(*ast.DropJavaActionStmt)) }) r.Register(&ast.CreateJavaActionStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateJavaAction(stmt.(*ast.CreateJavaActionStmt)) + return execCreateJavaAction(ctx, stmt.(*ast.CreateJavaActionStmt)) }) r.Register(&ast.DropFolderStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return ctx.executor.execDropFolder(stmt.(*ast.DropFolderStmt)) @@ -251,25 +251,25 @@ func registerODataHandlers(r *Registry) { func registerJSONStructureHandlers(r *Registry) { r.Register(&ast.CreateJsonStructureStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateJsonStructure(stmt.(*ast.CreateJsonStructureStmt)) + return execCreateJsonStructure(ctx, stmt.(*ast.CreateJsonStructureStmt)) }) r.Register(&ast.DropJsonStructureStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropJsonStructure(stmt.(*ast.DropJsonStructureStmt)) + return execDropJsonStructure(ctx, stmt.(*ast.DropJsonStructureStmt)) }) } func registerMappingHandlers(r *Registry) { r.Register(&ast.CreateImportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateImportMapping(stmt.(*ast.CreateImportMappingStmt)) + return execCreateImportMapping(ctx, stmt.(*ast.CreateImportMappingStmt)) }) r.Register(&ast.DropImportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropImportMapping(stmt.(*ast.DropImportMappingStmt)) + return execDropImportMapping(ctx, stmt.(*ast.DropImportMappingStmt)) }) r.Register(&ast.CreateExportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCreateExportMapping(stmt.(*ast.CreateExportMappingStmt)) + return execCreateExportMapping(ctx, stmt.(*ast.CreateExportMappingStmt)) }) r.Register(&ast.DropExportMappingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropExportMapping(stmt.(*ast.DropExportMappingStmt)) + return execDropExportMapping(ctx, stmt.(*ast.DropExportMappingStmt)) }) } From dfdc26b23658568d4882012b0f3cb6f858be2bc8 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 17:08:11 +0200 Subject: [PATCH 12/16] refactor: migrate cross-cutting handlers to free functions Migrate move/rename, folders, styling, widgets, features, diff, structure, mermaid, contract, context, search, lint, agent editor, languages, SQL, import, fragments, and misc handlers from Executor methods to free functions taking *ExecContext. 163 methods converted across 29 files. --- mdl/executor/cmd_agenteditor_agents.go | 94 +++++---- mdl/executor/cmd_agenteditor_kbs.go | 38 +++- mdl/executor/cmd_agenteditor_mcpservices.go | 38 +++- mdl/executor/cmd_agenteditor_models.go | 38 +++- mdl/executor/cmd_context.go | 163 +++++++++----- mdl/executor/cmd_contract.go | 208 +++++++++++------- mdl/executor/cmd_diff.go | 100 +++++---- mdl/executor/cmd_diff_local.go | 90 ++++---- mdl/executor/cmd_diff_mdl.go | 72 +++---- mdl/executor/cmd_diff_output.go | 54 ++--- mdl/executor/cmd_features.go | 51 +++-- mdl/executor/cmd_folders.go | 16 +- mdl/executor/cmd_fragments.go | 68 ++++-- mdl/executor/cmd_imagecollections.go | 7 +- mdl/executor/cmd_import.go | 59 +++--- mdl/executor/cmd_languages.go | 16 +- mdl/executor/cmd_lint.go | 64 +++--- mdl/executor/cmd_mermaid.go | 44 ++-- mdl/executor/cmd_misc.go | 78 +++++-- mdl/executor/cmd_module_overview.go | 22 +- mdl/executor/cmd_move.go | 80 ++++--- mdl/executor/cmd_oql_plan.go | 11 +- mdl/executor/cmd_rename.go | 72 ++++--- mdl/executor/cmd_search.go | 77 ++++--- mdl/executor/cmd_sql.go | 160 ++++++++------ mdl/executor/cmd_structure.go | 223 ++++++++++---------- mdl/executor/cmd_styling.go | 116 ++++++---- mdl/executor/cmd_widgets.go | 85 +++++--- mdl/executor/register_stubs.go | 60 +++--- 29 files changed, 1357 insertions(+), 847 deletions(-) diff --git a/mdl/executor/cmd_agenteditor_agents.go b/mdl/executor/cmd_agenteditor_agents.go index 2b7bd47c..d8f119a0 100644 --- a/mdl/executor/cmd_agenteditor_agents.go +++ b/mdl/executor/cmd_agenteditor_agents.go @@ -8,6 +8,7 @@ package executor import ( + "context" "fmt" "strings" @@ -17,7 +18,8 @@ import ( ) // showAgentEditorAgents handles SHOW AGENTS [IN module]. -func (e *Executor) showAgentEditorAgents(moduleName string) error { +func showAgentEditorAgents(ctx *ExecContext, moduleName string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -63,12 +65,13 @@ func (e *Executor) showAgentEditorAgents(moduleName string) error { // describeAgentEditorAgent handles DESCRIBE AGENT Module.Name. Emits a // round-trippable CREATE AGENT statement reflecting the Contents JSON. -func (e *Executor) describeAgentEditorAgent(name ast.QualifiedName) error { +func describeAgentEditorAgent(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } - a := e.findAgentEditorAgent(name.Module, name.Name) + a := findAgentEditorAgent(ctx, name.Module, name.Name) if a == nil { return mdlerrors.NewNotFound("agent", name.String()) } @@ -82,10 +85,10 @@ func (e *Executor) describeAgentEditorAgent(name ast.QualifiedName) error { qualifiedName := fmt.Sprintf("%s.%s", modName, a.Name) if a.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", a.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", a.Documentation) } - fmt.Fprintf(e.output, "CREATE AGENT %s (\n", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE AGENT %s (\n", qualifiedName) // Build property lines. User-set properties are emitted in a stable // order; empty values are omitted. @@ -134,102 +137,103 @@ func (e *Executor) describeAgentEditorAgent(name ast.QualifiedName) error { for i, line := range lines { if i < len(lines)-1 { - fmt.Fprintln(e.output, line+",") + fmt.Fprintln(ctx.Output, line+",") } else { - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } } // Body with TOOL / MCP SERVICE / KNOWLEDGE BASE blocks if present. hasBody := len(a.Tools) > 0 || len(a.KBTools) > 0 if hasBody { - fmt.Fprintln(e.output, ")") - fmt.Fprintln(e.output, "{") + fmt.Fprintln(ctx.Output, ")") + fmt.Fprintln(ctx.Output, "{") for i, t := range a.Tools { - emitToolBlock(e, t) + emitToolBlock(ctx, t) if i < len(a.Tools)-1 || len(a.KBTools) > 0 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } } for i, kb := range a.KBTools { - emitKBBlock(e, kb) + emitKBBlock(ctx, kb) if i < len(a.KBTools)-1 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } } - fmt.Fprintln(e.output, "};") + fmt.Fprintln(ctx.Output, "};") } else { - fmt.Fprintln(e.output, ");") + fmt.Fprintln(ctx.Output, ");") } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } // emitToolBlock writes one TOOL or MCP SERVICE block for the agent body. -func emitToolBlock(e *Executor, t agenteditor.AgentTool) { +func emitToolBlock(ctx *ExecContext, t agenteditor.AgentTool) { switch t.ToolType { case "MCP": if t.Document == nil { // malformed — skip return } - fmt.Fprintf(e.output, " MCP SERVICE %s {\n", t.Document.QualifiedName) - fmt.Fprintf(e.output, " Enabled: %t\n", t.Enabled) + fmt.Fprintf(ctx.Output, " MCP SERVICE %s {\n", t.Document.QualifiedName) + fmt.Fprintf(ctx.Output, " Enabled: %t\n", t.Enabled) if t.Description != "" { - fmt.Fprintf(e.output, " Description: '%s'\n", escapeSQLString(t.Description)) + fmt.Fprintf(ctx.Output, " Description: '%s'\n", escapeSQLString(t.Description)) } - fmt.Fprintln(e.output, " }") + fmt.Fprintln(ctx.Output, " }") default: // Microflow or unknown tool type — emit generic TOOL block. name := t.Name if name == "" { name = "Tool_" + strings.ReplaceAll(t.ID, "-", "")[:8] } - fmt.Fprintf(e.output, " TOOL %s {\n", name) + fmt.Fprintf(ctx.Output, " TOOL %s {\n", name) if t.ToolType != "" { - fmt.Fprintf(e.output, " ToolType: %s,\n", t.ToolType) + fmt.Fprintf(ctx.Output, " ToolType: %s,\n", t.ToolType) } if t.Document != nil && t.Document.QualifiedName != "" { - fmt.Fprintf(e.output, " Document: %s,\n", t.Document.QualifiedName) + fmt.Fprintf(ctx.Output, " Document: %s,\n", t.Document.QualifiedName) } - fmt.Fprintf(e.output, " Enabled: %t", t.Enabled) + fmt.Fprintf(ctx.Output, " Enabled: %t", t.Enabled) if t.Description != "" { - fmt.Fprintln(e.output, ",") - fmt.Fprintf(e.output, " Description: '%s'\n", escapeSQLString(t.Description)) + fmt.Fprintln(ctx.Output, ",") + fmt.Fprintf(ctx.Output, " Description: '%s'\n", escapeSQLString(t.Description)) } else { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, " }") + fmt.Fprintln(ctx.Output, " }") } } // emitKBBlock writes one KNOWLEDGE BASE block for the agent body. -func emitKBBlock(e *Executor, kb agenteditor.AgentKBTool) { +func emitKBBlock(ctx *ExecContext, kb agenteditor.AgentKBTool) { name := kb.Name if name == "" { name = "KB_" + strings.ReplaceAll(kb.ID, "-", "")[:8] } - fmt.Fprintf(e.output, " KNOWLEDGE BASE %s {\n", name) + fmt.Fprintf(ctx.Output, " KNOWLEDGE BASE %s {\n", name) if kb.Document != nil && kb.Document.QualifiedName != "" { - fmt.Fprintf(e.output, " Source: %s,\n", kb.Document.QualifiedName) + fmt.Fprintf(ctx.Output, " Source: %s,\n", kb.Document.QualifiedName) } if kb.CollectionIdentifier != "" { - fmt.Fprintf(e.output, " Collection: '%s',\n", escapeSQLString(kb.CollectionIdentifier)) + fmt.Fprintf(ctx.Output, " Collection: '%s',\n", escapeSQLString(kb.CollectionIdentifier)) } if kb.MaxResults != 0 { - fmt.Fprintf(e.output, " MaxResults: %d,\n", kb.MaxResults) + fmt.Fprintf(ctx.Output, " MaxResults: %d,\n", kb.MaxResults) } if kb.Description != "" { - fmt.Fprintf(e.output, " Description: '%s',\n", escapeSQLString(kb.Description)) + fmt.Fprintf(ctx.Output, " Description: '%s',\n", escapeSQLString(kb.Description)) } - fmt.Fprintf(e.output, " Enabled: %t\n", kb.Enabled) - fmt.Fprintln(e.output, " }") + fmt.Fprintf(ctx.Output, " Enabled: %t\n", kb.Enabled) + fmt.Fprintln(ctx.Output, " }") } // findAgentEditorAgent looks up an agent by module and name. -func (e *Executor) findAgentEditorAgent(moduleName, agentName string) *agenteditor.Agent { +func findAgentEditorAgent(ctx *ExecContext, moduleName, agentName string) *agenteditor.Agent { + e := ctx.executor agents, err := e.reader.ListAgentEditorAgents() if err != nil { return nil @@ -247,3 +251,17 @@ func (e *Executor) findAgentEditorAgent(moduleName, agentName string) *agentedit } return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) showAgentEditorAgents(moduleName string) error { + return showAgentEditorAgents(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeAgentEditorAgent(name ast.QualifiedName) error { + return describeAgentEditorAgent(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findAgentEditorAgent(moduleName, agentName string) *agenteditor.Agent { + return findAgentEditorAgent(e.newExecContext(context.Background()), moduleName, agentName) +} diff --git a/mdl/executor/cmd_agenteditor_kbs.go b/mdl/executor/cmd_agenteditor_kbs.go index 16a82597..cea24d97 100644 --- a/mdl/executor/cmd_agenteditor_kbs.go +++ b/mdl/executor/cmd_agenteditor_kbs.go @@ -9,6 +9,7 @@ package executor import ( + "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -17,7 +18,8 @@ import ( ) // showAgentEditorKnowledgeBases handles SHOW KNOWLEDGE BASES [IN module]. -func (e *Executor) showAgentEditorKnowledgeBases(moduleName string) error { +func showAgentEditorKnowledgeBases(ctx *ExecContext, moduleName string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -61,12 +63,13 @@ func (e *Executor) showAgentEditorKnowledgeBases(moduleName string) error { } // describeAgentEditorKnowledgeBase handles DESCRIBE KNOWLEDGE BASE Module.Name. -func (e *Executor) describeAgentEditorKnowledgeBase(name ast.QualifiedName) error { +func describeAgentEditorKnowledgeBase(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } - k := e.findAgentEditorKnowledgeBase(name.Module, name.Name) + k := findAgentEditorKnowledgeBase(ctx, name.Module, name.Name) if k == nil { return mdlerrors.NewNotFound("knowledge base", name.String()) } @@ -80,10 +83,10 @@ func (e *Executor) describeAgentEditorKnowledgeBase(name ast.QualifiedName) erro qualifiedName := fmt.Sprintf("%s.%s", modName, k.Name) if k.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", k.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", k.Documentation) } - fmt.Fprintf(e.output, "CREATE KNOWLEDGE BASE %s (\n", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE KNOWLEDGE BASE %s (\n", qualifiedName) var lines []string if k.Provider != "" { @@ -113,19 +116,20 @@ func (e *Executor) describeAgentEditorKnowledgeBase(name ast.QualifiedName) erro for i, line := range lines { if i < len(lines)-1 { - fmt.Fprintln(e.output, line+",") + fmt.Fprintln(ctx.Output, line+",") } else { - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } // findAgentEditorKnowledgeBase looks up a KB by module and name. -func (e *Executor) findAgentEditorKnowledgeBase(moduleName, kbName string) *agenteditor.KnowledgeBase { +func findAgentEditorKnowledgeBase(ctx *ExecContext, moduleName, kbName string) *agenteditor.KnowledgeBase { + e := ctx.executor kbs, err := e.reader.ListAgentEditorKnowledgeBases() if err != nil { return nil @@ -143,3 +147,17 @@ func (e *Executor) findAgentEditorKnowledgeBase(moduleName, kbName string) *agen } return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) showAgentEditorKnowledgeBases(moduleName string) error { + return showAgentEditorKnowledgeBases(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeAgentEditorKnowledgeBase(name ast.QualifiedName) error { + return describeAgentEditorKnowledgeBase(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findAgentEditorKnowledgeBase(moduleName, kbName string) *agenteditor.KnowledgeBase { + return findAgentEditorKnowledgeBase(e.newExecContext(context.Background()), moduleName, kbName) +} diff --git a/mdl/executor/cmd_agenteditor_mcpservices.go b/mdl/executor/cmd_agenteditor_mcpservices.go index 966fd94e..416cc4ec 100644 --- a/mdl/executor/cmd_agenteditor_mcpservices.go +++ b/mdl/executor/cmd_agenteditor_mcpservices.go @@ -9,6 +9,7 @@ package executor import ( + "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -17,7 +18,8 @@ import ( ) // showAgentEditorConsumedMCPServices handles SHOW CONSUMED MCP SERVICES [IN module]. -func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { +func showAgentEditorConsumedMCPServices(ctx *ExecContext, moduleName string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -57,12 +59,13 @@ func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { } // describeAgentEditorConsumedMCPService handles DESCRIBE CONSUMED MCP SERVICE Module.Name. -func (e *Executor) describeAgentEditorConsumedMCPService(name ast.QualifiedName) error { +func describeAgentEditorConsumedMCPService(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } - c := e.findAgentEditorConsumedMCPService(name.Module, name.Name) + c := findAgentEditorConsumedMCPService(ctx, name.Module, name.Name) if c == nil { return mdlerrors.NewNotFound("consumed MCP service", name.String()) } @@ -76,10 +79,10 @@ func (e *Executor) describeAgentEditorConsumedMCPService(name ast.QualifiedName) qualifiedName := fmt.Sprintf("%s.%s", modName, c.Name) if c.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", c.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", c.Documentation) } - fmt.Fprintf(e.output, "CREATE CONSUMED MCP SERVICE %s (\n", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE CONSUMED MCP SERVICE %s (\n", qualifiedName) var lines []string if c.ProtocolVersion != "" { @@ -97,19 +100,20 @@ func (e *Executor) describeAgentEditorConsumedMCPService(name ast.QualifiedName) for i, line := range lines { if i < len(lines)-1 { - fmt.Fprintln(e.output, line+",") + fmt.Fprintln(ctx.Output, line+",") } else { - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } // findAgentEditorConsumedMCPService looks up an MCP service by module and name. -func (e *Executor) findAgentEditorConsumedMCPService(moduleName, svcName string) *agenteditor.ConsumedMCPService { +func findAgentEditorConsumedMCPService(ctx *ExecContext, moduleName, svcName string) *agenteditor.ConsumedMCPService { + e := ctx.executor svcs, err := e.reader.ListAgentEditorConsumedMCPServices() if err != nil { return nil @@ -127,3 +131,17 @@ func (e *Executor) findAgentEditorConsumedMCPService(moduleName, svcName string) } return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { + return showAgentEditorConsumedMCPServices(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeAgentEditorConsumedMCPService(name ast.QualifiedName) error { + return describeAgentEditorConsumedMCPService(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findAgentEditorConsumedMCPService(moduleName, svcName string) *agenteditor.ConsumedMCPService { + return findAgentEditorConsumedMCPService(e.newExecContext(context.Background()), moduleName, svcName) +} diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index b7e877e8..9cbba94d 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -9,6 +9,7 @@ package executor import ( + "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -17,7 +18,8 @@ import ( ) // showAgentEditorModels handles SHOW MODELS [IN module]. -func (e *Executor) showAgentEditorModels(moduleName string) error { +func showAgentEditorModels(ctx *ExecContext, moduleName string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -64,12 +66,13 @@ func (e *Executor) showAgentEditorModels(moduleName string) error { // describeAgentEditorModel handles DESCRIBE MODEL Module.Name. // Emits a round-trippable CREATE MODEL statement. -func (e *Executor) describeAgentEditorModel(name ast.QualifiedName) error { +func describeAgentEditorModel(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } - m := e.findAgentEditorModel(name.Module, name.Name) + m := findAgentEditorModel(ctx, name.Module, name.Name) if m == nil { return mdlerrors.NewNotFound("model", name.String()) } @@ -83,10 +86,10 @@ func (e *Executor) describeAgentEditorModel(name ast.QualifiedName) error { qualifiedName := fmt.Sprintf("%s.%s", modName, m.Name) if m.Documentation != "" { - fmt.Fprintf(e.output, "/**\n * %s\n */\n", m.Documentation) + fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", m.Documentation) } - fmt.Fprintf(e.output, "CREATE MODEL %s (\n", qualifiedName) + fmt.Fprintf(ctx.Output, "CREATE MODEL %s (\n", qualifiedName) // Emit properties in stable order. User-set properties (Provider, Key) // come first; Portal-populated metadata comes last and only if non-empty. @@ -119,19 +122,20 @@ func (e *Executor) describeAgentEditorModel(name ast.QualifiedName) error { for i, line := range lines { if i < len(lines)-1 { - fmt.Fprintln(e.output, line+",") + fmt.Fprintln(ctx.Output, line+",") } else { - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } // findAgentEditorModel looks up a model by module and name. -func (e *Executor) findAgentEditorModel(moduleName, modelName string) *agenteditor.Model { +func findAgentEditorModel(ctx *ExecContext, moduleName, modelName string) *agenteditor.Model { + e := ctx.executor models, err := e.reader.ListAgentEditorModels() if err != nil { return nil @@ -149,3 +153,17 @@ func (e *Executor) findAgentEditorModel(moduleName, modelName string) *agentedit } return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) showAgentEditorModels(moduleName string) error { + return showAgentEditorModels(e.newExecContext(context.Background()), moduleName) +} + +func (e *Executor) describeAgentEditorModel(name ast.QualifiedName) error { + return describeAgentEditorModel(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findAgentEditorModel(moduleName, modelName string) *agenteditor.Model { + return findAgentEditorModel(e.newExecContext(context.Background()), moduleName, modelName) +} diff --git a/mdl/executor/cmd_context.go b/mdl/executor/cmd_context.go index 73d56c6c..6080dd3e 100644 --- a/mdl/executor/cmd_context.go +++ b/mdl/executor/cmd_context.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -12,7 +13,8 @@ import ( // execShowContext handles SHOW CONTEXT OF [DEPTH n] command. // It assembles relevant context information for LLM consumption. -func (e *Executor) execShowContext(s *ast.ShowStmt) error { +func execShowContext(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("SHOW CONTEXT requires a qualified name") } @@ -29,7 +31,7 @@ func (e *Executor) execShowContext(s *ast.ShowStmt) error { } // Detect the type of the target element - targetType, err := e.detectElementType(name) + targetType, err := detectElementType(ctx, name) if err != nil { return err } @@ -40,33 +42,33 @@ func (e *Executor) execShowContext(s *ast.ShowStmt) error { switch targetType { case "microflow", "nanoflow": - e.assembleMicroflowContext(&output, name, depth) + assembleMicroflowContext(ctx, &output, name, depth) case "entity": - e.assembleEntityContext(&output, name, depth) + assembleEntityContext(ctx, &output, name, depth) case "page": - e.assemblePageContext(&output, name, depth) + assemblePageContext(ctx, &output, name, depth) case "enumeration": - e.assembleEnumerationContext(&output, name) + assembleEnumerationContext(ctx, &output, name) case "workflow": - e.assembleWorkflowContext(&output, name, depth) + assembleWorkflowContext(ctx, &output, name, depth) case "snippet": - e.assembleSnippetContext(&output, name, depth) + assembleSnippetContext(ctx, &output, name, depth) case "javaaction": - e.assembleJavaActionContext(&output, name) + assembleJavaActionContext(ctx, &output, name) case "odataclient": - e.assembleODataClientContext(&output, name) + assembleODataClientContext(ctx, &output, name) case "odataservice": - e.assembleODataServiceContext(&output, name) + assembleODataServiceContext(ctx, &output, name) default: output.WriteString(fmt.Sprintf("Unknown element type for: %s\n", name)) } - fmt.Fprint(e.output, output.String()) + fmt.Fprint(ctx.Output, output.String()) return nil } // detectElementType determines what kind of element the name refers to. -func (e *Executor) detectElementType(name string) (string, error) { +func detectElementType(ctx *ExecContext, name string) (string, error) { // Check catalog tables for known element types catalogChecks := []struct { table string @@ -84,7 +86,7 @@ func (e *Executor) detectElementType(name string) (string, error) { } for _, check := range catalogChecks { - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT 1 FROM %s WHERE QualifiedName = '%s' LIMIT 1", check.table, name)) if err == nil && result.Count > 0 { return check.elemType, nil @@ -95,10 +97,10 @@ func (e *Executor) detectElementType(name string) (string, error) { } // assembleMicroflowContext assembles context for a microflow. -func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, depth int) { +func assembleMicroflowContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { // Get microflow basic info out.WriteString("### Microflow Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ReturnType, ParameterCount, ActivityCount FROM microflows WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -111,7 +113,7 @@ func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, d // Entities used by this microflow out.WriteString("### Entities Used\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName, RefKind FROM refs WHERE SourceName = '%s' AND TargetType = 'entity' ORDER BY RefKind, TargetName`, name)) @@ -128,7 +130,7 @@ func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, d // Pages shown by this microflow out.WriteString("### Pages Shown\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND RefKind = 'show_page' ORDER BY TargetName`, name)) @@ -144,13 +146,13 @@ func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, d // Called microflows (with depth) out.WriteString(fmt.Sprintf("### Called Microflows (depth %d)\n\n", depth)) if depth > 0 { - e.addCallees(out, name, depth, 1) + addCallees(ctx, out, name, depth, 1) } out.WriteString("\n") // Direct callers out.WriteString("### Direct Callers\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT SourceName FROM refs WHERE TargetName = '%s' AND RefKind = 'call' ORDER BY SourceName LIMIT 10`, name)) @@ -167,13 +169,13 @@ func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, d } // addCallees recursively adds callees up to the specified depth. -func (e *Executor) addCallees(out *strings.Builder, name string, maxDepth, currentDepth int) { +func addCallees(ctx *ExecContext, out *strings.Builder, name string, maxDepth, currentDepth int) { if currentDepth > maxDepth { return } indent := strings.Repeat(" ", currentDepth-1) - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND RefKind = 'call' ORDER BY TargetName`, name)) @@ -186,16 +188,16 @@ func (e *Executor) addCallees(out *strings.Builder, name string, maxDepth, curre out.WriteString(fmt.Sprintf("%s- %s\n", indent, callee)) // Recurse for deeper levels if currentDepth < maxDepth { - e.addCallees(out, callee, maxDepth, currentDepth+1) + addCallees(ctx, out, callee, maxDepth, currentDepth+1) } } } // assembleEntityContext assembles context for an entity. -func (e *Executor) assembleEntityContext(out *strings.Builder, name string, depth int) { +func assembleEntityContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { // Get entity basic info out.WriteString("### Entity Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, EntityType, Generalization, AttributeCount, IndexCount FROM entities WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -211,7 +213,7 @@ func (e *Executor) assembleEntityContext(out *strings.Builder, name string, dept // Microflows that use this entity out.WriteString("### Microflows Using This Entity\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName, RefKind FROM refs WHERE TargetName = '%s' AND SourceType = 'microflow' ORDER BY RefKind, SourceName LIMIT 20`, name)) @@ -231,7 +233,7 @@ func (e *Executor) assembleEntityContext(out *strings.Builder, name string, dept // Pages displaying this entity out.WriteString("### Pages Displaying This Entity\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName FROM refs WHERE TargetName = '%s' AND SourceType = 'page' ORDER BY SourceName LIMIT 10`, name)) @@ -246,7 +248,7 @@ func (e *Executor) assembleEntityContext(out *strings.Builder, name string, dept // Related entities (via associations or generalization) out.WriteString("### Related Entities\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName, RefKind FROM refs WHERE SourceName = '%s' AND TargetType = 'entity' UNION @@ -263,10 +265,10 @@ func (e *Executor) assembleEntityContext(out *strings.Builder, name string, dept } // assemblePageContext assembles context for a page. -func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth int) { +func assemblePageContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { // Get page basic info out.WriteString("### Page Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, Title, URL, LayoutRef, WidgetCount FROM pages WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -286,7 +288,7 @@ func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth // Entities used on this page out.WriteString("### Entities Used\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND TargetType = 'entity' ORDER BY TargetName`, name)) @@ -301,7 +303,7 @@ func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth // Microflows called from this page out.WriteString("### Microflows Called\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND TargetType = 'microflow' ORDER BY TargetName LIMIT 15`, name)) @@ -316,7 +318,7 @@ func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth // Microflows that show this page out.WriteString("### Shown By\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT SourceName FROM refs WHERE TargetName = '%s' AND RefKind = 'show_page' ORDER BY SourceName LIMIT 10`, name)) @@ -330,10 +332,10 @@ func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth } // assembleEnumerationContext assembles context for an enumeration. -func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) { +func assembleEnumerationContext(ctx *ExecContext, out *strings.Builder, name string) { // Get enumeration basic info out.WriteString("### Enumeration Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ValueCount FROM enumerations WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -344,7 +346,7 @@ func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) // Entities with attributes of this enumeration type out.WriteString("### Used By Entities\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName FROM refs WHERE TargetName = '%s' AND SourceType = 'entity' ORDER BY SourceName LIMIT 15`, name)) @@ -359,7 +361,7 @@ func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) // Microflows that use this enumeration out.WriteString("### Used By Microflows\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName FROM refs WHERE TargetName = '%s' AND SourceType = 'microflow' ORDER BY SourceName LIMIT 15`, name)) @@ -373,9 +375,10 @@ func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) } // assembleSnippetContext assembles context for a snippet. -func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, depth int) { +func assembleSnippetContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { + e := ctx.executor out.WriteString("### Snippet Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ParameterCount, WidgetCount FROM snippets WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -402,7 +405,7 @@ func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, dep // Pages that use this snippet out.WriteString("### Used By Pages\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName FROM refs WHERE TargetName = '%s' AND RefKind = 'snippet_call' ORDER BY SourceName LIMIT 15`, name)) @@ -416,7 +419,8 @@ func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, dep } // assembleJavaActionContext assembles context for a java action. -func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) { +func assembleJavaActionContext(ctx *ExecContext, out *strings.Builder, name string) { + e := ctx.executor out.WriteString("### Java Action Definition\n\n```sql\n") parts := strings.SplitN(name, ".", 2) if len(parts) == 2 { @@ -433,7 +437,7 @@ func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) // Microflows that call this java action out.WriteString("### Called By Microflows\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT SourceName FROM refs WHERE TargetName = '%s' AND RefKind = 'call' ORDER BY SourceName LIMIT 15`, name)) @@ -447,9 +451,9 @@ func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) } // assembleODataClientContext assembles context for a consumed OData service. -func (e *Executor) assembleODataClientContext(out *strings.Builder, name string) { +func assembleODataClientContext(ctx *ExecContext, out *strings.Builder, name string) { out.WriteString("### Consumed OData Service\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, Version, ODataVersion, MetadataUrl FROM odata_clients WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -462,7 +466,7 @@ func (e *Executor) assembleODataClientContext(out *strings.Builder, name string) // External entities from this service out.WriteString("### External Entities\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND RefKind = 'odata_entity' ORDER BY TargetName LIMIT 15`, name)) @@ -476,10 +480,11 @@ func (e *Executor) assembleODataClientContext(out *strings.Builder, name string) } // assembleWorkflowContext assembles context for a workflow. -func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, depth int) { +func assembleWorkflowContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { + e := ctx.executor // Get workflow basic info out.WriteString("### Workflow Definition\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ParameterEntity, ActivityCount, UserTaskCount, MicroflowCallCount, DecisionCount, Description FROM workflows WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -514,7 +519,7 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de // Microflows called by this workflow out.WriteString("### Microflows Called\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName, RefKind FROM refs WHERE SourceName = '%s' AND TargetType = 'MICROFLOW' ORDER BY RefKind, TargetName`, name)) @@ -531,7 +536,7 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de // Pages used by this workflow (user task pages, overview page) out.WriteString("### Pages Used\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName, RefKind FROM refs WHERE SourceName = '%s' AND TargetType = 'PAGE' ORDER BY TargetName`, name)) @@ -546,7 +551,7 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de // Entities referenced by this workflow out.WriteString("### Entities Used\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName, RefKind FROM refs WHERE SourceName = '%s' AND TargetType = 'ENTITY' ORDER BY TargetName`, name)) @@ -561,7 +566,7 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de // Direct callers (what calls this workflow) out.WriteString("### Direct Callers\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT SourceName, SourceType FROM refs WHERE TargetName = '%s' ORDER BY SourceName LIMIT 15`, name)) @@ -578,9 +583,9 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de } // assembleODataServiceContext assembles context for a published OData service. -func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string) { +func assembleODataServiceContext(ctx *ExecContext, out *strings.Builder, name string) { out.WriteString("### Published OData Service\n\n") - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, Path, Version, ODataVersion, EntitySetCount FROM odata_services WHERE QualifiedName = '%s'", name)) if err == nil && result.Count > 0 { row := result.Rows[0] @@ -594,7 +599,7 @@ func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string // Published entities out.WriteString("### Published Entities\n\n") - result, err = e.catalog.Query(fmt.Sprintf( + result, err = ctx.Catalog.Query(fmt.Sprintf( `SELECT DISTINCT TargetName FROM refs WHERE SourceName = '%s' AND RefKind = 'odata_publish' ORDER BY TargetName LIMIT 15`, name)) @@ -606,3 +611,53 @@ func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string out.WriteString("(none found)\n") } } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) execShowContext(s *ast.ShowStmt) error { + return execShowContext(e.newExecContext(context.Background()), s) +} + +func (e *Executor) detectElementType(name string) (string, error) { + return detectElementType(e.newExecContext(context.Background()), name) +} + +func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, depth int) { + assembleMicroflowContext(e.newExecContext(context.Background()), out, name, depth) +} + +func (e *Executor) addCallees(out *strings.Builder, name string, maxDepth, currentDepth int) { + addCallees(e.newExecContext(context.Background()), out, name, maxDepth, currentDepth) +} + +func (e *Executor) assembleEntityContext(out *strings.Builder, name string, depth int) { + assembleEntityContext(e.newExecContext(context.Background()), out, name, depth) +} + +func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth int) { + assemblePageContext(e.newExecContext(context.Background()), out, name, depth) +} + +func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) { + assembleEnumerationContext(e.newExecContext(context.Background()), out, name) +} + +func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, depth int) { + assembleSnippetContext(e.newExecContext(context.Background()), out, name, depth) +} + +func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) { + assembleJavaActionContext(e.newExecContext(context.Background()), out, name) +} + +func (e *Executor) assembleODataClientContext(out *strings.Builder, name string) { + assembleODataClientContext(e.newExecContext(context.Background()), out, name) +} + +func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, depth int) { + assembleWorkflowContext(e.newExecContext(context.Background()), out, name, depth) +} + +func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string) { + assembleODataServiceContext(e.newExecContext(context.Background()), out, name) +} diff --git a/mdl/executor/cmd_contract.go b/mdl/executor/cmd_contract.go index c9ed342d..b9890c74 100644 --- a/mdl/executor/cmd_contract.go +++ b/mdl/executor/cmd_contract.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -15,12 +16,13 @@ import ( ) // showContractEntities handles SHOW CONTRACT ENTITIES FROM Module.Service. -func (e *Executor) showContractEntities(name *ast.QualifiedName) error { +func showContractEntities(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT ENTITIES FROM Module.Service") } - doc, svcQN, err := e.parseServiceContract(*name) + doc, svcQN, err := parseServiceContract(ctx, *name) if err != nil { return err } @@ -56,7 +58,7 @@ func (e *Executor) showContractEntities(name *ast.QualifiedName) error { } if len(rows) == 0 { - fmt.Fprintf(e.output, "No entity types found in contract for %s.\n", svcQN) + fmt.Fprintf(ctx.Output, "No entity types found in contract for %s.\n", svcQN) return nil } @@ -75,18 +77,19 @@ func (e *Executor) showContractEntities(name *ast.QualifiedName) error { } // showContractActions handles SHOW CONTRACT ACTIONS FROM Module.Service. -func (e *Executor) showContractActions(name *ast.QualifiedName) error { +func showContractActions(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT ACTIONS FROM Module.Service") } - doc, svcQN, err := e.parseServiceContract(*name) + doc, svcQN, err := parseServiceContract(ctx, *name) if err != nil { return err } if len(doc.Actions) == 0 { - fmt.Fprintf(e.output, "No actions/functions found in contract for %s.\n", svcQN) + fmt.Fprintf(ctx.Output, "No actions/functions found in contract for %s.\n", svcQN) return nil } @@ -139,7 +142,7 @@ func (e *Executor) showContractActions(name *ast.QualifiedName) error { } // describeContractEntity handles DESCRIBE CONTRACT ENTITY Service.EntityName [FORMAT mdl]. -func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) error { +func describeContractEntity(ctx *ExecContext, name ast.QualifiedName, format string) error { // Name is Module.Service.EntityName — split into service ref and entity name // or Module.Service (list all) — but DESCRIBE should have a specific entity svcName, entityName, err := splitContractRef(name) @@ -147,7 +150,7 @@ func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) return err } - doc, svcQN, err := e.parseServiceContract(svcName) + doc, svcQN, err := parseServiceContract(ctx, svcName) if err != nil { return err } @@ -158,18 +161,18 @@ func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) } if strings.EqualFold(format, "mdl") { - return e.outputContractEntityMDL(et, svcQN, doc) + return outputContractEntityMDL(ctx, et, svcQN, doc) } // Default: human-readable format - fmt.Fprintf(e.output, "%s (Key: %s)\n", et.Name, strings.Join(et.KeyProperties, ", ")) + fmt.Fprintf(ctx.Output, "%s (Key: %s)\n", et.Name, strings.Join(et.KeyProperties, ", ")) if et.Summary != "" { - fmt.Fprintf(e.output, " Summary: %s\n", et.Summary) + fmt.Fprintf(ctx.Output, " Summary: %s\n", et.Summary) } if et.Description != "" { - fmt.Fprintf(e.output, " Description: %s\n", et.Description) + fmt.Fprintf(ctx.Output, " Description: %s\n", et.Description) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Properties nameWidth := len("Property") @@ -184,20 +187,20 @@ func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) } } - fmt.Fprintf(e.output, " %-*s %-*s %s\n", nameWidth, "Property", typeWidth, "Type", "Nullable") - fmt.Fprintf(e.output, " %s %s %s\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth), "--------") + fmt.Fprintf(ctx.Output, " %-*s %-*s %s\n", nameWidth, "Property", typeWidth, "Type", "Nullable") + fmt.Fprintf(ctx.Output, " %s %s %s\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth), "--------") for _, p := range et.Properties { nullable := "Yes" if p.Nullable != nil && !*p.Nullable { nullable = "No" } - fmt.Fprintf(e.output, " %-*s %-*s %s\n", nameWidth, p.Name, typeWidth, formatEdmType(p), nullable) + fmt.Fprintf(ctx.Output, " %-*s %-*s %s\n", nameWidth, p.Name, typeWidth, formatEdmType(p), nullable) } // Navigation properties if len(et.NavigationProperties) > 0 { - fmt.Fprintln(e.output) - fmt.Fprintln(e.output, " Navigation Properties:") + fmt.Fprintln(ctx.Output) + fmt.Fprintln(ctx.Output, " Navigation Properties:") for _, nav := range et.NavigationProperties { multiplicity := "0..1" if nav.IsMany { @@ -207,7 +210,7 @@ func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) if target == "" && nav.ToRole != "" { target = nav.ToRole } - fmt.Fprintf(e.output, " → %-20s (%s %s)\n", nav.Name, target, multiplicity) + fmt.Fprintf(ctx.Output, " → %-20s (%s %s)\n", nav.Name, target, multiplicity) } } @@ -215,13 +218,13 @@ func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) } // describeContractAction handles DESCRIBE CONTRACT ACTION Service.ActionName [FORMAT mdl]. -func (e *Executor) describeContractAction(name ast.QualifiedName, format string) error { +func describeContractAction(ctx *ExecContext, name ast.QualifiedName, format string) error { svcName, actionName, err := splitContractRef(name) if err != nil { return err } - doc, svcQN, err := e.parseServiceContract(svcName) + doc, svcQN, err := parseServiceContract(ctx, svcName) if err != nil { return err } @@ -237,33 +240,33 @@ func (e *Executor) describeContractAction(name ast.QualifiedName, format string) return mdlerrors.NewNotFoundMsg("action", actionName, fmt.Sprintf("action %q not found in contract for %s", actionName, svcQN)) } - fmt.Fprintf(e.output, "%s\n", action.Name) + fmt.Fprintf(ctx.Output, "%s\n", action.Name) if action.IsBound { - fmt.Fprintln(e.output, " Bound: Yes") + fmt.Fprintln(ctx.Output, " Bound: Yes") } if len(action.Parameters) > 0 { - fmt.Fprintln(e.output, " Parameters:") + fmt.Fprintln(ctx.Output, " Parameters:") for _, p := range action.Parameters { nullable := "" if p.Nullable != nil && !*p.Nullable { nullable = " NOT NULL" } - fmt.Fprintf(e.output, " %-20s %s%s\n", p.Name, shortenEdmType(p.Type), nullable) + fmt.Fprintf(ctx.Output, " %-20s %s%s\n", p.Name, shortenEdmType(p.Type), nullable) } } if action.ReturnType != "" { - fmt.Fprintf(e.output, " Returns: %s\n", shortenEdmType(action.ReturnType)) + fmt.Fprintf(ctx.Output, " Returns: %s\n", shortenEdmType(action.ReturnType)) } else { - fmt.Fprintln(e.output, " Returns: (void)") + fmt.Fprintln(ctx.Output, " Returns: (void)") } return nil } // outputContractEntityMDL outputs a CREATE EXTERNAL ENTITY statement from contract metadata. -func (e *Executor) outputContractEntityMDL(et *mpr.EdmEntityType, svcQN string, doc *mpr.EdmxDocument) error { +func outputContractEntityMDL(ctx *ExecContext, et *mpr.EdmEntityType, svcQN string, doc *mpr.EdmxDocument) error { // Find entity set name entitySetName := et.Name + "s" // fallback for _, es := range doc.EntitySets { @@ -279,13 +282,13 @@ func (e *Executor) outputContractEntityMDL(et *mpr.EdmEntityType, svcQN string, module = svcQN[:idx] } - fmt.Fprintf(e.output, "CREATE EXTERNAL ENTITY %s.%s\n", module, et.Name) - fmt.Fprintf(e.output, "FROM ODATA CLIENT %s (\n", svcQN) - fmt.Fprintf(e.output, " EntitySet: '%s',\n", entitySetName) - fmt.Fprintf(e.output, " RemoteName: '%s',\n", et.Name) - fmt.Fprintf(e.output, " Countable: Yes\n") - fmt.Fprintln(e.output, ")") - fmt.Fprintln(e.output, "(") + fmt.Fprintf(ctx.Output, "CREATE EXTERNAL ENTITY %s.%s\n", module, et.Name) + fmt.Fprintf(ctx.Output, "FROM ODATA CLIENT %s (\n", svcQN) + fmt.Fprintf(ctx.Output, " EntitySet: '%s',\n", entitySetName) + fmt.Fprintf(ctx.Output, " RemoteName: '%s',\n", et.Name) + fmt.Fprintf(ctx.Output, " Countable: Yes\n") + fmt.Fprintln(ctx.Output, ")") + fmt.Fprintln(ctx.Output, "(") for i, p := range et.Properties { // Skip ID properties that are not real attributes @@ -305,17 +308,18 @@ func (e *Executor) outputContractEntityMDL(et *mpr.EdmEntityType, svcQN string, if i == len(et.Properties)-1 { comma = "" } - fmt.Fprintf(e.output, " %s: %s%s\n", p.Name, mendixType, comma) + fmt.Fprintf(ctx.Output, " %s: %s%s\n", p.Name, mendixType, comma) } - fmt.Fprintln(e.output, ");") - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, ");") + fmt.Fprintln(ctx.Output, "/") return nil } // parseServiceContract finds a consumed OData service by name and parses its cached $metadata. -func (e *Executor) parseServiceContract(name ast.QualifiedName) (*mpr.EdmxDocument, string, error) { +func parseServiceContract(ctx *ExecContext, name ast.QualifiedName) (*mpr.EdmxDocument, string, error) { + e := ctx.executor services, err := e.reader.ListConsumedODataServices() if err != nil { return nil, "", mdlerrors.NewBackend("list consumed OData services", err) @@ -445,12 +449,13 @@ var reservedEntityAttrNames = map[string]bool{ // It reads entity types from the cached $metadata and creates external entities in the domain model, // populating Source, Key, and per-attribute RemoteName/RemoteType fields so the resulting BSON matches // what Studio Pro produces. -func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) error { +func createExternalEntities(ctx *ExecContext, s *ast.CreateExternalEntitiesStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnectedWrite() } - doc, svcQN, err := e.parseServiceContract(s.ServiceRef) + doc, svcQN, err := parseServiceContract(ctx, s.ServiceRef) if err != nil { return err } @@ -627,13 +632,13 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err if existingEntity, ok := existing[mendixName]; ok { if !s.CreateOrModify { - fmt.Fprintf(e.output, " SKIPPED: %s.%s (already exists; use CREATE OR MODIFY to update)\n", targetModule, mendixName) + fmt.Fprintf(ctx.Output, " SKIPPED: %s.%s (already exists; use CREATE OR MODIFY to update)\n", targetModule, mendixName) skipped++ continue } applyExternalEntityFields(existingEntity, et, isTopLevel, serviceRef, entitySet, keyParts, attrs) if err := e.writer.UpdateEntity(dm.ID, existingEntity); err != nil { - fmt.Fprintf(e.output, " FAILED: %s.%s — %v\n", targetModule, mendixName, err) + fmt.Fprintf(ctx.Output, " FAILED: %s.%s — %v\n", targetModule, mendixName, err) failed++ continue } @@ -649,7 +654,7 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err newEntity.ID = model.ID(mpr.GenerateID()) applyExternalEntityFields(newEntity, et, isTopLevel, serviceRef, entitySet, keyParts, attrs) if err := e.writer.CreateEntity(dm.ID, newEntity); err != nil { - fmt.Fprintf(e.output, " FAILED: %s.%s — %v\n", targetModule, mendixName, err) + fmt.Fprintf(ctx.Output, " FAILED: %s.%s — %v\n", targetModule, mendixName, err) failed++ continue } @@ -662,9 +667,9 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err // parent entity to each NPE. dm, err = e.reader.GetDomainModel(module.ID) if err == nil { - npesCreated := e.createPrimitiveCollectionNPEs(dm, doc, typeByQualified, esMap, serviceRef) + npesCreated := createPrimitiveCollectionNPEs(ctx, dm, doc, typeByQualified, esMap, serviceRef) if npesCreated > 0 { - fmt.Fprintf(e.output, "Created %d primitive-collection NPEs\n", npesCreated) + fmt.Fprintf(ctx.Output, "Created %d primitive-collection NPEs\n", npesCreated) } } @@ -673,13 +678,13 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err // from the previous pass are visible. dm, err = e.reader.GetDomainModel(module.ID) if err == nil { - assocsCreated := e.createNavigationAssociations(dm, doc, typeByQualified, esMap, serviceRef) + assocsCreated := createNavigationAssociations(ctx, dm, doc, typeByQualified, esMap, serviceRef) if assocsCreated > 0 { - fmt.Fprintf(e.output, "Created %d navigation associations\n", assocsCreated) + fmt.Fprintf(ctx.Output, "Created %d navigation associations\n", assocsCreated) } } - fmt.Fprintf(e.output, "\nFrom %s into %s: %d created, %d updated, %d skipped, %d failed\n", + fmt.Fprintf(ctx.Output, "\nFrom %s into %s: %d created, %d updated, %d skipped, %d failed\n", svcQN, targetModule, created, updated, skipped, failed) return nil @@ -696,13 +701,15 @@ type assocKey struct { // the values plus an association from the parent entity. This mirrors how // Studio Pro handles e.g. Trip.Tags = Collection(Edm.String) by creating a // TripTag NPE and a Trip_TripTag ReferenceSet. -func (e *Executor) createPrimitiveCollectionNPEs( +func createPrimitiveCollectionNPEs( + ctx *ExecContext, dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string, ) int { + e := ctx.executor // Lookup parent Mendix entity by EDMX type qualified name. parentByQN := make(map[string]*domainmodel.Entity) for qn, et := range typeByQualified { @@ -785,7 +792,7 @@ func (e *Executor) createPrimitiveCollectionNPEs( npe.ID = model.ID(mpr.GenerateID()) if err := e.writer.CreateEntity(dm.ID, npe); err != nil { - fmt.Fprintf(e.output, " NPE FAILED: %s — %v\n", npeName, err) + fmt.Fprintf(ctx.Output, " NPE FAILED: %s — %v\n", npeName, err) continue } count++ @@ -806,7 +813,7 @@ func (e *Executor) createPrimitiveCollectionNPEs( } assoc.ID = model.ID(mpr.GenerateID()) if err := e.writer.CreateAssociation(dm.ID, assoc); err != nil { - fmt.Fprintf(e.output, " NPE ASSOC FAILED: %s — %v\n", assocName, err) + fmt.Fprintf(ctx.Output, " NPE ASSOC FAILED: %s — %v\n", assocName, err) } } } @@ -878,13 +885,15 @@ func singular(name string) string { // CreatableFromParent / UpdatableFromParent come from the entity set's // Org.OData.Capabilities.V1.{Insert,Update}Restrictions/Non*NavigationProperties // annotations. -func (e *Executor) createNavigationAssociations( +func createNavigationAssociations( + ctx *ExecContext, dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string, ) int { + e := ctx.executor // Build per-entity-type lookup of nav property name → restricted flags, // plus a direct entity-set lookup so we can read the base Insertable / // Updatable defaults for the FROM entity. @@ -1028,7 +1037,7 @@ func (e *Executor) createNavigationAssociations( assoc.ID = model.ID(mpr.GenerateID()) if err := e.writer.CreateAssociation(dm.ID, assoc); err != nil { - fmt.Fprintf(e.output, " ASSOC FAILED: %s.%s — %v\n", parentEnt.Name, assocName, err) + fmt.Fprintf(ctx.Output, " ASSOC FAILED: %s.%s — %v\n", parentEnt.Name, assocName, err) continue } existingAssocs[assocKey{parentEnt.Name, assocName}] = true @@ -1244,18 +1253,19 @@ func edmToAstDataType(p *mpr.EdmProperty) ast.DataType { // ============================================================================ // showContractChannels handles SHOW CONTRACT CHANNELS FROM Module.Service. -func (e *Executor) showContractChannels(name *ast.QualifiedName) error { +func showContractChannels(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT CHANNELS FROM Module.Service") } - doc, svcQN, err := e.parseAsyncAPIContract(*name) + doc, svcQN, err := parseAsyncAPIContract(ctx, *name) if err != nil { return err } if len(doc.Channels) == 0 { - fmt.Fprintf(e.output, "No channels found in contract for %s.\n", svcQN) + fmt.Fprintf(ctx.Output, "No channels found in contract for %s.\n", svcQN) return nil } @@ -1283,18 +1293,19 @@ func (e *Executor) showContractChannels(name *ast.QualifiedName) error { } // showContractMessages handles SHOW CONTRACT MESSAGES FROM Module.Service. -func (e *Executor) showContractMessages(name *ast.QualifiedName) error { +func showContractMessages(ctx *ExecContext, name *ast.QualifiedName) error { + e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT MESSAGES FROM Module.Service") } - doc, svcQN, err := e.parseAsyncAPIContract(*name) + doc, svcQN, err := parseAsyncAPIContract(ctx, *name) if err != nil { return err } if len(doc.Messages) == 0 { - fmt.Fprintf(e.output, "No messages found in contract for %s.\n", svcQN) + fmt.Fprintf(ctx.Output, "No messages found in contract for %s.\n", svcQN) return nil } @@ -1326,13 +1337,13 @@ func (e *Executor) showContractMessages(name *ast.QualifiedName) error { } // describeContractMessage handles DESCRIBE CONTRACT MESSAGE Module.Service.MessageName. -func (e *Executor) describeContractMessage(name ast.QualifiedName) error { +func describeContractMessage(ctx *ExecContext, name ast.QualifiedName) error { svcName, msgName, err := splitContractRef(name) if err != nil { return err } - doc, svcQN, err := e.parseAsyncAPIContract(svcName) + doc, svcQN, err := parseAsyncAPIContract(ctx, svcName) if err != nil { return err } @@ -1342,19 +1353,19 @@ func (e *Executor) describeContractMessage(name ast.QualifiedName) error { return mdlerrors.NewNotFoundMsg("message", msgName, fmt.Sprintf("message %q not found in contract for %s", msgName, svcQN)) } - fmt.Fprintf(e.output, "%s\n", msg.Name) + fmt.Fprintf(ctx.Output, "%s\n", msg.Name) if msg.Title != "" { - fmt.Fprintf(e.output, " Title: %s\n", msg.Title) + fmt.Fprintf(ctx.Output, " Title: %s\n", msg.Title) } if msg.Description != "" { - fmt.Fprintf(e.output, " Description: %s\n", msg.Description) + fmt.Fprintf(ctx.Output, " Description: %s\n", msg.Description) } if msg.ContentType != "" { - fmt.Fprintf(e.output, " ContentType: %s\n", msg.ContentType) + fmt.Fprintf(ctx.Output, " ContentType: %s\n", msg.ContentType) } if len(msg.Properties) > 0 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) nameWidth := len("Property") typeWidth := len("Type") for _, p := range msg.Properties { @@ -1367,10 +1378,10 @@ func (e *Executor) describeContractMessage(name ast.QualifiedName) error { } } - fmt.Fprintf(e.output, " %-*s %-*s\n", nameWidth, "Property", typeWidth, "Type") - fmt.Fprintf(e.output, " %s %s\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth)) + fmt.Fprintf(ctx.Output, " %-*s %-*s\n", nameWidth, "Property", typeWidth, "Type") + fmt.Fprintf(ctx.Output, " %s %s\n", strings.Repeat("-", nameWidth), strings.Repeat("-", typeWidth)) for _, p := range msg.Properties { - fmt.Fprintf(e.output, " %-*s %-*s\n", nameWidth, p.Name, typeWidth, asyncTypeString(p)) + fmt.Fprintf(ctx.Output, " %-*s %-*s\n", nameWidth, p.Name, typeWidth, asyncTypeString(p)) } } @@ -1378,7 +1389,8 @@ func (e *Executor) describeContractMessage(name ast.QualifiedName) error { } // parseAsyncAPIContract finds a business event service by name and parses its cached AsyncAPI document. -func (e *Executor) parseAsyncAPIContract(name ast.QualifiedName) (*mpr.AsyncAPIDocument, string, error) { +func parseAsyncAPIContract(ctx *ExecContext, name ast.QualifiedName) (*mpr.AsyncAPIDocument, string, error) { + e := ctx.executor services, err := e.reader.ListBusinessEventServices() if err != nil { return nil, "", mdlerrors.NewBackend("list business event services", err) @@ -1421,3 +1433,57 @@ func asyncTypeString(p *mpr.AsyncAPIProperty) string { } return p.Type } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) showContractEntities(name *ast.QualifiedName) error { + return showContractEntities(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showContractActions(name *ast.QualifiedName) error { + return showContractActions(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) error { + return describeContractEntity(e.newExecContext(context.Background()), name, format) +} + +func (e *Executor) describeContractAction(name ast.QualifiedName, format string) error { + return describeContractAction(e.newExecContext(context.Background()), name, format) +} + +func (e *Executor) outputContractEntityMDL(et *mpr.EdmEntityType, svcQN string, doc *mpr.EdmxDocument) error { + return outputContractEntityMDL(e.newExecContext(context.Background()), et, svcQN, doc) +} + +func (e *Executor) parseServiceContract(name ast.QualifiedName) (*mpr.EdmxDocument, string, error) { + return parseServiceContract(e.newExecContext(context.Background()), name) +} + +func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) error { + return createExternalEntities(e.newExecContext(context.Background()), s) +} + +func (e *Executor) createPrimitiveCollectionNPEs(dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string) int { + return createPrimitiveCollectionNPEs(e.newExecContext(context.Background()), dm, doc, typeByQualified, esMap, serviceRef) +} + +func (e *Executor) createNavigationAssociations(dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string) int { + return createNavigationAssociations(e.newExecContext(context.Background()), dm, doc, typeByQualified, esMap, serviceRef) +} + +func (e *Executor) showContractChannels(name *ast.QualifiedName) error { + return showContractChannels(e.newExecContext(context.Background()), name) +} + +func (e *Executor) showContractMessages(name *ast.QualifiedName) error { + return showContractMessages(e.newExecContext(context.Background()), name) +} + +func (e *Executor) describeContractMessage(name ast.QualifiedName) error { + return describeContractMessage(e.newExecContext(context.Background()), name) +} + +func (e *Executor) parseAsyncAPIContract(name ast.QualifiedName) (*mpr.AsyncAPIDocument, string, error) { + return parseAsyncAPIContract(e.newExecContext(context.Background()), name) +} diff --git a/mdl/executor/cmd_diff.go b/mdl/executor/cmd_diff.go index 87824a9b..8631a341 100644 --- a/mdl/executor/cmd_diff.go +++ b/mdl/executor/cmd_diff.go @@ -5,6 +5,7 @@ package executor import ( "bytes" + "context" "fmt" "strings" @@ -66,7 +67,8 @@ const ( ) // DiffProgram compares an MDL program against the current project state -func (e *Executor) DiffProgram(prog *ast.Program, opts DiffOptions) error { +func diffProgram(ctx *ExecContext, prog *ast.Program, opts DiffOptions) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -87,7 +89,7 @@ func (e *Executor) DiffProgram(prog *ast.Program, opts DiffOptions) error { // Process each statement for _, stmt := range prog.Statements { - result, err := e.diffStatement(stmt) + result, err := diffStatement(ctx, stmt) if err != nil { // Skip statements that can't be diffed (e.g., connection statements) continue @@ -123,45 +125,51 @@ func (e *Executor) DiffProgram(prog *ast.Program, opts DiffOptions) error { switch opts.Format { case DiffFormatUnified: - e.outputUnifiedDiff(result, opts.UseColor) + outputUnifiedDiff(ctx, result, opts.UseColor) case DiffFormatSideBySide: - e.outputSideBySideDiff(result, opts.Width, opts.UseColor) + outputSideBySideDiff(ctx, result, opts.Width, opts.UseColor) case DiffFormatStructural: - e.outputStructuralDiff(result, opts.UseColor) + outputStructuralDiff(ctx, result, opts.UseColor) } } // Output summary - fmt.Fprintf(e.output, "\nSummary: %d new, %d modified, %d unchanged\n", + fmt.Fprintf(ctx.Output, "\nSummary: %d new, %d modified, %d unchanged\n", newCount, modifiedCount, unchangedCount) return nil } +// DiffProgram is a method wrapper for external callers. +func (e *Executor) DiffProgram(prog *ast.Program, opts DiffOptions) error { + return diffProgram(e.newExecContext(context.Background()), prog, opts) +} + // diffStatement generates a diff result for a single statement -func (e *Executor) diffStatement(stmt ast.Statement) (*DiffResult, error) { +func diffStatement(ctx *ExecContext, stmt ast.Statement) (*DiffResult, error) { switch s := stmt.(type) { case *ast.CreateEntityStmt: - return e.diffEntity(s) + return diffEntity(ctx, s) case *ast.CreateViewEntityStmt: - return e.diffViewEntity(s) + return diffViewEntity(ctx, s) case *ast.CreateEnumerationStmt: - return e.diffEnumeration(s) + return diffEnumeration(ctx, s) case *ast.CreateAssociationStmt: - return e.diffAssociation(s) + return diffAssociation(ctx, s) case *ast.CreateMicroflowStmt: - return e.diffMicroflow(s) + return diffMicroflow(ctx, s) default: return nil, nil // Skip unsupported statements } } // diffEntity compares a CREATE ENTITY statement against the project -func (e *Executor) diffEntity(s *ast.CreateEntityStmt) (*DiffResult, error) { +func diffEntity(ctx *ExecContext, s *ast.CreateEntityStmt) (*DiffResult, error) { + e := ctx.executor result := &DiffResult{ ObjectType: "Entity", ObjectName: s.Name, - Proposed: e.entityStmtToMDL(s), + Proposed: entityStmtToMDL(ctx, s), } // Try to find existing entity @@ -180,8 +188,8 @@ func (e *Executor) diffEntity(s *ast.CreateEntityStmt) (*DiffResult, error) { for _, entity := range dm.Entities { if entity.Name == s.Name.Name { // Found existing entity - get its MDL representation - result.Current = e.entityToMDL(module.Name, entity, dm) - result.Changes = e.compareEntities(result.Current, result.Proposed) + result.Current = entityToMDL(ctx, module.Name, entity, dm) + result.Changes = compareEntities(ctx, result.Current, result.Proposed) return result, nil } } @@ -191,11 +199,12 @@ func (e *Executor) diffEntity(s *ast.CreateEntityStmt) (*DiffResult, error) { } // diffViewEntity compares a CREATE VIEW ENTITY statement against the project -func (e *Executor) diffViewEntity(s *ast.CreateViewEntityStmt) (*DiffResult, error) { +func diffViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) (*DiffResult, error) { + e := ctx.executor result := &DiffResult{ ObjectType: "View Entity", ObjectName: s.Name, - Proposed: e.viewEntityStmtToMDL(s), + Proposed: viewEntityStmtToMDL(ctx, s), } module, err := e.findModule(s.Name.Module) @@ -212,7 +221,7 @@ func (e *Executor) diffViewEntity(s *ast.CreateViewEntityStmt) (*DiffResult, err for _, entity := range dm.Entities { if entity.Name == s.Name.Name { - result.Current = e.viewEntityFromProjectToMDL(module.Name, entity, dm) + result.Current = viewEntityFromProjectToMDL(ctx, module.Name, entity, dm) return result, nil } } @@ -222,11 +231,12 @@ func (e *Executor) diffViewEntity(s *ast.CreateViewEntityStmt) (*DiffResult, err } // diffEnumeration compares a CREATE ENUMERATION statement against the project -func (e *Executor) diffEnumeration(s *ast.CreateEnumerationStmt) (*DiffResult, error) { +func diffEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) (*DiffResult, error) { + e := ctx.executor result := &DiffResult{ ObjectType: "Enumeration", ObjectName: s.Name, - Proposed: e.enumerationStmtToMDL(s), + Proposed: enumerationStmtToMDL(ctx, s), } // Try to find existing enumeration @@ -238,18 +248,19 @@ func (e *Executor) diffEnumeration(s *ast.CreateEnumerationStmt) (*DiffResult, e h, _ := e.getHierarchy() modName := h.GetModuleName(existingEnum.ContainerID) - result.Current = e.enumerationToMDL(modName, existingEnum) - result.Changes = e.compareEnumerations(result.Current, result.Proposed) + result.Current = enumerationToMDL(ctx, modName, existingEnum) + result.Changes = compareEnumerations(ctx, result.Current, result.Proposed) return result, nil } // diffAssociation compares a CREATE ASSOCIATION statement against the project -func (e *Executor) diffAssociation(s *ast.CreateAssociationStmt) (*DiffResult, error) { +func diffAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) (*DiffResult, error) { + e := ctx.executor result := &DiffResult{ ObjectType: "Association", ObjectName: s.Name, - Proposed: e.associationStmtToMDL(s), + Proposed: associationStmtToMDL(ctx, s), } module, err := e.findModule(s.Name.Module) @@ -266,7 +277,7 @@ func (e *Executor) diffAssociation(s *ast.CreateAssociationStmt) (*DiffResult, e for _, assoc := range dm.Associations { if assoc.Name == s.Name.Name { - result.Current = e.associationToMDL(module.Name, assoc, dm) + result.Current = associationToMDL(ctx, module.Name, assoc, dm) return result, nil } } @@ -276,11 +287,12 @@ func (e *Executor) diffAssociation(s *ast.CreateAssociationStmt) (*DiffResult, e } // diffMicroflow compares a CREATE MICROFLOW statement against the project -func (e *Executor) diffMicroflow(s *ast.CreateMicroflowStmt) (*DiffResult, error) { +func diffMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) (*DiffResult, error) { + e := ctx.executor result := &DiffResult{ ObjectType: "Microflow", ObjectName: s.Name, - Proposed: e.microflowStmtToMDL(s), + Proposed: microflowStmtToMDL(ctx, s), } // Try to find existing microflow @@ -307,7 +319,7 @@ func (e *Executor) diffMicroflow(s *ast.CreateMicroflowStmt) (*DiffResult, error e.describeMicroflow(s.Name) e.output = oldOutput result.Current = strings.TrimSuffix(buf.String(), "\n") - result.Changes = e.compareMicroflows(result.Current, result.Proposed) + result.Changes = compareMicroflows(ctx, result.Current, result.Proposed) return result, nil } } @@ -321,7 +333,7 @@ func (e *Executor) diffMicroflow(s *ast.CreateMicroflowStmt) (*DiffResult, error // ============================================================================ // compareEntities extracts structural changes between two entity MDL representations -func (e *Executor) compareEntities(current, proposed string) []StructuralChange { +func compareEntities(ctx *ExecContext, current, proposed string) []StructuralChange { var changes []StructuralChange // Simple line-based comparison for now @@ -329,8 +341,8 @@ func (e *Executor) compareEntities(current, proposed string) []StructuralChange proposedLines := strings.Split(proposed, "\n") // Extract attributes from both - currentAttrs := e.extractAttributes(currentLines) - proposedAttrs := e.extractAttributes(proposedLines) + currentAttrs := extractAttributes(ctx, currentLines) + proposedAttrs := extractAttributes(ctx, proposedLines) // Find added attributes for name, proposed := range proposedAttrs { @@ -371,11 +383,11 @@ func (e *Executor) compareEntities(current, proposed string) []StructuralChange } // compareEnumerations extracts structural changes between two enumeration MDL representations -func (e *Executor) compareEnumerations(current, proposed string) []StructuralChange { +func compareEnumerations(ctx *ExecContext, current, proposed string) []StructuralChange { var changes []StructuralChange - currentValues := e.extractEnumValues(strings.Split(current, "\n")) - proposedValues := e.extractEnumValues(strings.Split(proposed, "\n")) + currentValues := extractEnumValues(ctx, strings.Split(current, "\n")) + proposedValues := extractEnumValues(ctx, strings.Split(proposed, "\n")) for name := range proposedValues { if _, exists := currentValues[name]; !exists { @@ -401,11 +413,11 @@ func (e *Executor) compareEnumerations(current, proposed string) []StructuralCha } // compareMicroflows extracts structural changes between two microflow MDL representations -func (e *Executor) compareMicroflows(current, proposed string) []StructuralChange { +func compareMicroflows(ctx *ExecContext, current, proposed string) []StructuralChange { var changes []StructuralChange - currentParams := e.extractParameters(strings.Split(current, "\n")) - proposedParams := e.extractParameters(strings.Split(proposed, "\n")) + currentParams := extractParameters(ctx, strings.Split(current, "\n")) + proposedParams := extractParameters(ctx, strings.Split(proposed, "\n")) for name := range proposedParams { if _, exists := currentParams[name]; !exists { @@ -428,8 +440,8 @@ func (e *Executor) compareMicroflows(current, proposed string) []StructuralChang } // Count body statements - currentStmts := e.countBodyStatements(current) - proposedStmts := e.countBodyStatements(proposed) + currentStmts := countBodyStatements(ctx, current) + proposedStmts := countBodyStatements(ctx, proposed) if currentStmts != proposedStmts { diff := proposedStmts - currentStmts if diff > 0 { @@ -453,7 +465,7 @@ func (e *Executor) compareMicroflows(current, proposed string) []StructuralChang } // extractAttributes extracts attribute definitions from MDL lines -func (e *Executor) extractAttributes(lines []string) map[string]string { +func extractAttributes(_ *ExecContext, lines []string) map[string]string { attrs := make(map[string]string) for _, line := range lines { line = strings.TrimSpace(line) @@ -471,7 +483,7 @@ func (e *Executor) extractAttributes(lines []string) map[string]string { } // extractEnumValues extracts enumeration values from MDL lines -func (e *Executor) extractEnumValues(lines []string) map[string]bool { +func extractEnumValues(_ *ExecContext, lines []string) map[string]bool { values := make(map[string]bool) for _, line := range lines { line = strings.TrimSpace(line) @@ -489,7 +501,7 @@ func (e *Executor) extractEnumValues(lines []string) map[string]bool { } // extractParameters extracts parameter names from MDL lines -func (e *Executor) extractParameters(lines []string) map[string]bool { +func extractParameters(_ *ExecContext, lines []string) map[string]bool { params := make(map[string]bool) inParams := false for _, line := range lines { @@ -517,7 +529,7 @@ func (e *Executor) extractParameters(lines []string) map[string]bool { } // countBodyStatements counts statements in a microflow body -func (e *Executor) countBodyStatements(mdl string) int { +func countBodyStatements(_ *ExecContext, mdl string) int { count := 0 inBody := false for line := range strings.SplitSeq(mdl, "\n") { diff --git a/mdl/executor/cmd_diff_local.go b/mdl/executor/cmd_diff_local.go index b3adb348..8d13f8b6 100644 --- a/mdl/executor/cmd_diff_local.go +++ b/mdl/executor/cmd_diff_local.go @@ -4,6 +4,7 @@ package executor import ( + "context" "encoding/base64" "fmt" "os" @@ -22,13 +23,14 @@ import ( // Local Git Diff Functions // ============================================================================ -// DiffLocal compares local changes in mxunit files against a git reference. +// diffLocal compares local changes in mxunit files against a git reference. // This only works with MPR v2 format (Mendix 10.18+) which stores units in mprcontents/. // // The ref parameter can be: // - A single ref (e.g., "HEAD", "main") — compares working tree vs ref // - A range "base..target" — compares two revisions (no working tree) -func (e *Executor) DiffLocal(ref string, opts DiffOptions) error { +func diffLocal(ctx *ExecContext, ref string, opts DiffOptions) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -52,13 +54,13 @@ func (e *Executor) DiffLocal(ref string, opts DiffOptions) error { } // Find changed mxunit files using git - changedFiles, err := e.findChangedMxunitFiles(contentsDir, ref) + changedFiles, err := findChangedMxunitFiles(ctx, contentsDir, ref) if err != nil { return mdlerrors.NewBackend("find changed files", err) } if len(changedFiles) == 0 { - fmt.Fprintln(e.output, "No local changes found in mxunit files.") + fmt.Fprintln(ctx.Output, "No local changes found in mxunit files.") return nil } @@ -66,10 +68,10 @@ func (e *Executor) DiffLocal(ref string, opts DiffOptions) error { var newCount, modifiedCount, deletedCount int for _, change := range changedFiles { - result, err := e.diffMxunitFile(change, contentsDir, ref) + result, err := diffMxunitFile(ctx, change, contentsDir, ref) if err != nil { // Log error but continue with other files - fmt.Fprintf(e.output, "Warning: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: %v\n", err) continue } if result != nil { @@ -88,21 +90,26 @@ func (e *Executor) DiffLocal(ref string, opts DiffOptions) error { for _, result := range results { switch opts.Format { case DiffFormatUnified: - e.outputUnifiedDiff(result, opts.UseColor) + outputUnifiedDiff(ctx, result, opts.UseColor) case DiffFormatSideBySide: - e.outputSideBySideDiff(result, opts.Width, opts.UseColor) + outputSideBySideDiff(ctx, result, opts.Width, opts.UseColor) case DiffFormatStructural: - e.outputStructuralDiff(result, opts.UseColor) + outputStructuralDiff(ctx, result, opts.UseColor) } } // Output summary - fmt.Fprintf(e.output, "\nSummary: %d new, %d modified, %d deleted\n", + fmt.Fprintf(ctx.Output, "\nSummary: %d new, %d modified, %d deleted\n", newCount, modifiedCount, deletedCount) return nil } +// DiffLocal is a method wrapper for external callers. +func (e *Executor) DiffLocal(ref string, opts DiffOptions) error { + return diffLocal(e.newExecContext(context.Background()), ref, opts) +} + // gitChange represents a file change from git type gitChange struct { Status string // "A" (added), "M" (modified), "D" (deleted) @@ -110,7 +117,7 @@ type gitChange struct { } // findChangedMxunitFiles uses git to find changed mxunit files -func (e *Executor) findChangedMxunitFiles(contentsDir, ref string) ([]gitChange, error) { +func findChangedMxunitFiles(_ *ExecContext, contentsDir, ref string) ([]gitChange, error) { // Run git diff to find changed files in mprcontents cmd := execCommand("git", "diff", "--name-status", ref, "--", contentsDir) output, err := cmd.Output() @@ -151,7 +158,7 @@ func (e *Executor) findChangedMxunitFiles(contentsDir, ref string) ([]gitChange, // diffMxunitFile generates a diff for a single mxunit file. // For two-revision diffs (ref contains ".."), both sides are read from git. // For single-ref diffs, the "current" side is read from the working tree. -func (e *Executor) diffMxunitFile(change gitChange, contentsDir, ref string) (*DiffResult, error) { +func diffMxunitFile(ctx *ExecContext, change gitChange, contentsDir, ref string) (*DiffResult, error) { var currentContent, gitContent []byte var err error @@ -213,15 +220,15 @@ func (e *Executor) diffMxunitFile(change gitChange, contentsDir, ref string) (*D // Generate MDL for both versions based on type if len(currentContent) > 0 { - result.Proposed = e.bsonToMDL(unitType, unitID, currentContent) + result.Proposed = bsonToMDL(ctx, unitType, unitID, currentContent) } if len(gitContent) > 0 { - result.Current = e.bsonToMDL(unitType, unitID, gitContent) + result.Current = bsonToMDL(ctx, unitType, unitID, gitContent) } // Generate structural changes if !result.IsNew && !result.IsDeleted && result.Current != result.Proposed { - result.Changes = e.compareGeneric(result.Current, result.Proposed) + result.Changes = compareGeneric(ctx, result.Current, result.Proposed) } return &result, nil @@ -259,7 +266,8 @@ func extractUUIDFromPath(path string) string { } // bsonToMDL converts BSON content to MDL representation based on type -func (e *Executor) bsonToMDL(unitType, unitID string, content []byte) string { +func bsonToMDL(ctx *ExecContext, unitType, unitID string, content []byte) string { + e := ctx.executor var raw map[string]any if err := bson.Unmarshal(content, &raw); err != nil { return fmt.Sprintf("-- Error parsing BSON: %v", err) @@ -286,23 +294,23 @@ func (e *Executor) bsonToMDL(unitType, unitID string, content []byte) string { switch { case strings.Contains(unitType, "DomainModel"): - return e.domainModelBsonToMDL(raw, qualifiedName) + return domainModelBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Entity"): - return e.entityBsonToMDL(raw, qualifiedName) + return entityBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Microflow"): - return e.microflowBsonToMDL(raw, qualifiedName) + return microflowBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Nanoflow"): - return e.nanoflowBsonToMDL(raw, qualifiedName) + return nanoflowBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Enumeration"): - return e.enumerationBsonToMDL(raw, qualifiedName) + return enumerationBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Page"): - return e.pageBsonToMDL(raw, qualifiedName) + return pageBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Snippet"): - return e.snippetBsonToMDL(raw, qualifiedName) + return snippetBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Layout"): - return e.layoutBsonToMDL(raw, qualifiedName) + return layoutBsonToMDL(ctx, raw, qualifiedName) case strings.Contains(unitType, "Module"): - return e.moduleBsonToMDL(raw) + return moduleBsonToMDL(ctx, raw) default: // Generic representation return fmt.Sprintf("-- %s: %s\n-- Type: %s", simplifyTypeName(unitType), qualifiedName, unitType) @@ -311,7 +319,7 @@ func (e *Executor) bsonToMDL(unitType, unitID string, content []byte) string { // domainModelBsonToMDL converts a domain model BSON to MDL. // Includes full entity definitions (attributes) so diffs show schema changes. -func (e *Executor) domainModelBsonToMDL(raw map[string]any, name string) string { +func domainModelBsonToMDL(ctx *ExecContext, raw map[string]any, name string) string { var lines []string lines = append(lines, fmt.Sprintf("-- Domain Model: %s", name)) lines = append(lines, "") @@ -325,7 +333,7 @@ func (e *Executor) domainModelBsonToMDL(raw map[string]any, name string) string continue } qn := name + "." + entName - lines = append(lines, e.entityBsonToMDL(entMap, qn)) + lines = append(lines, entityBsonToMDL(ctx, entMap, qn)) lines = append(lines, "") } } @@ -356,7 +364,7 @@ func (e *Executor) domainModelBsonToMDL(raw map[string]any, name string) string } // entityBsonToMDL converts an entity BSON to MDL -func (e *Executor) entityBsonToMDL(raw map[string]any, qualifiedName string) string { +func entityBsonToMDL(ctx *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string // Documentation @@ -378,7 +386,7 @@ func (e *Executor) entityBsonToMDL(raw map[string]any, qualifiedName string) str attributes := extractBsonArray(raw["Attributes"]) for i, attr := range attributes { if attrMap, ok := attr.(map[string]any); ok { - attrLine := e.attributeBsonToMDL(attrMap) + attrLine := attributeBsonToMDL(ctx, attrMap) comma := "," if i == len(attributes)-1 { comma = "" @@ -394,7 +402,7 @@ func (e *Executor) entityBsonToMDL(raw map[string]any, qualifiedName string) str } // attributeBsonToMDL converts an attribute BSON to MDL line -func (e *Executor) attributeBsonToMDL(raw map[string]any) string { +func attributeBsonToMDL(_ *ExecContext, raw map[string]any) string { name := extractString(raw["Name"]) typeStr := "Unknown" @@ -489,11 +497,12 @@ func (e *Executor) attributeBsonToMDL(raw map[string]any) string { // microflowBsonToMDL converts a microflow BSON to MDL using the same // renderer as DESCRIBE MICROFLOW, so diffs include activity bodies. // Falls back to a header-only stub if parsing fails. -func (e *Executor) microflowBsonToMDL(raw map[string]any, qualifiedName string) string { +func microflowBsonToMDL(ctx *ExecContext, raw map[string]any, qualifiedName string) string { + e := ctx.executor qn := splitQualifiedName(qualifiedName) mf := mpr.ParseMicroflowFromRaw(raw, model.ID(qn.Name), "") - entityNames, microflowNames := e.buildNameLookups() + entityNames, microflowNames := buildNameLookups(ctx) return e.renderMicroflowMDL(mf, qn, entityNames, microflowNames, nil) } @@ -510,7 +519,8 @@ func splitQualifiedName(qualifiedName string) ast.QualifiedName { // microflows from the current project. Used by BSON-driven renderers that // receive IDs (e.g. entity references) and want to resolve them against // the working-tree model. Returns empty maps if the reader is unavailable. -func (e *Executor) buildNameLookups() (map[model.ID]string, map[model.ID]string) { +func buildNameLookups(ctx *ExecContext) (map[model.ID]string, map[model.ID]string) { + e := ctx.executor entityNames := make(map[model.ID]string) microflowNames := make(map[model.ID]string) if e.reader == nil { @@ -537,7 +547,7 @@ func (e *Executor) buildNameLookups() (map[model.ID]string, map[model.ID]string) } // nanoflowBsonToMDL converts a nanoflow BSON to MDL -func (e *Executor) nanoflowBsonToMDL(raw map[string]any, qualifiedName string) string { +func nanoflowBsonToMDL(_ *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string if doc := extractString(raw["Documentation"]); doc != "" { @@ -563,7 +573,7 @@ func (e *Executor) nanoflowBsonToMDL(raw map[string]any, qualifiedName string) s } // enumerationBsonToMDL converts an enumeration BSON to MDL -func (e *Executor) enumerationBsonToMDL(raw map[string]any, qualifiedName string) string { +func enumerationBsonToMDL(_ *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string if doc := extractString(raw["Documentation"]); doc != "" { @@ -597,7 +607,7 @@ func (e *Executor) enumerationBsonToMDL(raw map[string]any, qualifiedName string } // pageBsonToMDL converts a page BSON to MDL -func (e *Executor) pageBsonToMDL(raw map[string]any, qualifiedName string) string { +func pageBsonToMDL(_ *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string if doc := extractString(raw["Documentation"]); doc != "" { @@ -628,7 +638,7 @@ func (e *Executor) pageBsonToMDL(raw map[string]any, qualifiedName string) strin } // snippetBsonToMDL converts a snippet BSON to MDL -func (e *Executor) snippetBsonToMDL(raw map[string]any, qualifiedName string) string { +func snippetBsonToMDL(_ *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string if doc := extractString(raw["Documentation"]); doc != "" { @@ -647,7 +657,7 @@ func (e *Executor) snippetBsonToMDL(raw map[string]any, qualifiedName string) st } // layoutBsonToMDL converts a layout BSON to MDL -func (e *Executor) layoutBsonToMDL(raw map[string]any, qualifiedName string) string { +func layoutBsonToMDL(_ *ExecContext, raw map[string]any, qualifiedName string) string { var lines []string if doc := extractString(raw["Documentation"]); doc != "" { @@ -666,7 +676,7 @@ func (e *Executor) layoutBsonToMDL(raw map[string]any, qualifiedName string) str } // moduleBsonToMDL converts a module BSON to MDL -func (e *Executor) moduleBsonToMDL(raw map[string]any) string { +func moduleBsonToMDL(_ *ExecContext, raw map[string]any) string { name := extractString(raw["Name"]) var lines []string @@ -683,7 +693,7 @@ func (e *Executor) moduleBsonToMDL(raw map[string]any) string { } // compareGeneric provides a generic comparison for MDL content -func (e *Executor) compareGeneric(current, proposed string) []StructuralChange { +func compareGeneric(_ *ExecContext, current, proposed string) []StructuralChange { var changes []StructuralChange currentLines := strings.Split(current, "\n") diff --git a/mdl/executor/cmd_diff_mdl.go b/mdl/executor/cmd_diff_mdl.go index a4bd45be..d2b7a12e 100644 --- a/mdl/executor/cmd_diff_mdl.go +++ b/mdl/executor/cmd_diff_mdl.go @@ -17,7 +17,7 @@ import ( // ============================================================================ // entityStmtToMDL converts a CreateEntityStmt to MDL text -func (e *Executor) entityStmtToMDL(s *ast.CreateEntityStmt) string { +func entityStmtToMDL(ctx *ExecContext, s *ast.CreateEntityStmt) string { var lines []string // Documentation @@ -43,7 +43,7 @@ func (e *Executor) entityStmtToMDL(s *ast.CreateEntityStmt) string { lines = append(lines, fmt.Sprintf(" /** %s */", attr.Documentation)) } - typeStr := e.dataTypeToString(attr.Type) + typeStr := dataTypeToString(ctx, attr.Type) constraints := "" if attr.NotNull { @@ -95,7 +95,7 @@ func (e *Executor) entityStmtToMDL(s *ast.CreateEntityStmt) string { } // viewEntityStmtToMDL converts a CreateViewEntityStmt to MDL text -func (e *Executor) viewEntityStmtToMDL(s *ast.CreateViewEntityStmt) string { +func viewEntityStmtToMDL(ctx *ExecContext, s *ast.CreateViewEntityStmt) string { var lines []string if s.Documentation != "" { @@ -111,7 +111,7 @@ func (e *Executor) viewEntityStmtToMDL(s *ast.CreateViewEntityStmt) string { lines = append(lines, fmt.Sprintf("CREATE VIEW ENTITY %s (", s.Name)) for i, attr := range s.Attributes { - typeStr := e.dataTypeToString(attr.Type) + typeStr := dataTypeToString(ctx, attr.Type) comma := "," if i == len(s.Attributes)-1 { comma = "" @@ -131,7 +131,7 @@ func (e *Executor) viewEntityStmtToMDL(s *ast.CreateViewEntityStmt) string { } // enumerationStmtToMDL converts a CreateEnumerationStmt to MDL text -func (e *Executor) enumerationStmtToMDL(s *ast.CreateEnumerationStmt) string { +func enumerationStmtToMDL(ctx *ExecContext, s *ast.CreateEnumerationStmt) string { var lines []string if s.Documentation != "" { @@ -157,7 +157,7 @@ func (e *Executor) enumerationStmtToMDL(s *ast.CreateEnumerationStmt) string { } // associationStmtToMDL converts a CreateAssociationStmt to MDL text -func (e *Executor) associationStmtToMDL(s *ast.CreateAssociationStmt) string { +func associationStmtToMDL(ctx *ExecContext, s *ast.CreateAssociationStmt) string { var lines []string if s.Documentation != "" { @@ -195,7 +195,7 @@ func (e *Executor) associationStmtToMDL(s *ast.CreateAssociationStmt) string { } // microflowStmtToMDL converts a CreateMicroflowStmt to MDL text -func (e *Executor) microflowStmtToMDL(s *ast.CreateMicroflowStmt) string { +func microflowStmtToMDL(ctx *ExecContext, s *ast.CreateMicroflowStmt) string { var lines []string // Documentation @@ -211,7 +211,7 @@ func (e *Executor) microflowStmtToMDL(s *ast.CreateMicroflowStmt) string { if len(s.Parameters) > 0 { lines = append(lines, fmt.Sprintf("CREATE MICROFLOW %s (", s.Name)) for i, param := range s.Parameters { - paramType := e.dataTypeToString(param.Type) + paramType := dataTypeToString(ctx, param.Type) comma := "," if i == len(s.Parameters)-1 { comma = "" @@ -225,7 +225,7 @@ func (e *Executor) microflowStmtToMDL(s *ast.CreateMicroflowStmt) string { // Return type if s.ReturnType != nil { - returnType := e.dataTypeToString(s.ReturnType.Type) + returnType := dataTypeToString(ctx, s.ReturnType.Type) if returnType != "Void" && returnType != "" { returnLine := fmt.Sprintf("RETURNS %s", returnType) if s.ReturnType.Variable != "" { @@ -240,7 +240,7 @@ func (e *Executor) microflowStmtToMDL(s *ast.CreateMicroflowStmt) string { // Body statements for _, stmt := range s.Body { - stmtLines := e.microflowStatementToMDL(stmt, 1) + stmtLines := microflowStatementToMDL(ctx, stmt, 1) lines = append(lines, stmtLines...) } @@ -251,25 +251,25 @@ func (e *Executor) microflowStmtToMDL(s *ast.CreateMicroflowStmt) string { } // microflowStatementToMDL converts a microflow statement to MDL lines -func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent int) []string { +func microflowStatementToMDL(ctx *ExecContext, stmt ast.MicroflowStatement, indent int) []string { indentStr := strings.Repeat(" ", indent) var lines []string switch s := stmt.(type) { case *ast.DeclareStmt: - typeStr := e.dataTypeToString(s.Type) + typeStr := dataTypeToString(ctx, s.Type) initVal := "empty" if s.InitialValue != nil { - initVal = e.expressionToString(s.InitialValue) + initVal = diffExpressionToString(ctx, s.InitialValue) } lines = append(lines, fmt.Sprintf("%sDECLARE $%s %s = %s;", indentStr, s.Variable, typeStr, initVal)) case *ast.MfSetStmt: - lines = append(lines, fmt.Sprintf("%sSET $%s = %s;", indentStr, s.Target, e.expressionToString(s.Value))) + lines = append(lines, fmt.Sprintf("%sSET $%s = %s;", indentStr, s.Target, diffExpressionToString(ctx, s.Value))) case *ast.ReturnStmt: if s.Value != nil { - lines = append(lines, fmt.Sprintf("%sRETURN %s;", indentStr, e.expressionToString(s.Value))) + lines = append(lines, fmt.Sprintf("%sRETURN %s;", indentStr, diffExpressionToString(ctx, s.Value))) } else { lines = append(lines, fmt.Sprintf("%sRETURN;", indentStr)) } @@ -278,7 +278,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i if len(s.Changes) > 0 { var members []string for _, c := range s.Changes { - members = append(members, fmt.Sprintf("%s = %s", c.Attribute, e.expressionToString(c.Value))) + members = append(members, fmt.Sprintf("%s = %s", c.Attribute, diffExpressionToString(ctx, c.Value))) } lines = append(lines, fmt.Sprintf("%s$%s = CREATE %s (%s);", indentStr, s.Variable, s.EntityType, strings.Join(members, ", "))) } else { @@ -289,7 +289,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i if len(s.Changes) > 0 { var members []string for _, c := range s.Changes { - members = append(members, fmt.Sprintf("%s = %s", c.Attribute, e.expressionToString(c.Value))) + members = append(members, fmt.Sprintf("%s = %s", c.Attribute, diffExpressionToString(ctx, c.Value))) } lines = append(lines, fmt.Sprintf("%sCHANGE $%s (%s);", indentStr, s.Variable, strings.Join(members, ", "))) } else { @@ -317,7 +317,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i stmt = fmt.Sprintf("%sRETRIEVE $%s FROM %s", indentStr, s.Variable, s.Source) } if s.Where != nil { - stmt += fmt.Sprintf("\n%s WHERE %s", indentStr, e.expressionToString(s.Where)) + stmt += fmt.Sprintf("\n%s WHERE %s", indentStr, diffExpressionToString(ctx, s.Where)) } if s.Limit != "" { stmt += fmt.Sprintf("\n%s LIMIT %s", indentStr, s.Limit) @@ -325,14 +325,14 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i lines = append(lines, stmt+";") case *ast.IfStmt: - lines = append(lines, fmt.Sprintf("%sIF %s THEN", indentStr, e.expressionToString(s.Condition))) + lines = append(lines, fmt.Sprintf("%sIF %s THEN", indentStr, diffExpressionToString(ctx, s.Condition))) for _, thenStmt := range s.ThenBody { - lines = append(lines, e.microflowStatementToMDL(thenStmt, indent+1)...) + lines = append(lines, microflowStatementToMDL(ctx, thenStmt, indent+1)...) } if len(s.ElseBody) > 0 { lines = append(lines, indentStr+"ELSE") for _, elseStmt := range s.ElseBody { - lines = append(lines, e.microflowStatementToMDL(elseStmt, indent+1)...) + lines = append(lines, microflowStatementToMDL(ctx, elseStmt, indent+1)...) } } lines = append(lines, indentStr+"END IF;") @@ -340,7 +340,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i case *ast.LoopStmt: lines = append(lines, fmt.Sprintf("%sLOOP $%s IN $%s", indentStr, s.LoopVariable, s.ListVariable)) for _, bodyStmt := range s.Body { - lines = append(lines, e.microflowStatementToMDL(bodyStmt, indent+1)...) + lines = append(lines, microflowStatementToMDL(ctx, bodyStmt, indent+1)...) } lines = append(lines, indentStr+"END LOOP;") @@ -349,12 +349,12 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i if !strings.HasPrefix(nodeStr, "'") { nodeStr = "'" + nodeStr + "'" } - msgStr := e.expressionToString(s.Message) + msgStr := diffExpressionToString(ctx, s.Message) stmt := fmt.Sprintf("%sLOG %s NODE %s %s", indentStr, strings.ToUpper(s.Level.String()), nodeStr, msgStr) if len(s.Template) > 0 { var params []string for _, p := range s.Template { - params = append(params, fmt.Sprintf("{%d} = %s", p.Index, e.expressionToString(p.Value))) + params = append(params, fmt.Sprintf("{%d} = %s", p.Index, diffExpressionToString(ctx, p.Value))) } stmt += fmt.Sprintf(" WITH (%s)", strings.Join(params, ", ")) } @@ -363,7 +363,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i case *ast.CallMicroflowStmt: var params []string for _, arg := range s.Arguments { - params = append(params, fmt.Sprintf("%s = %s", arg.Name, e.expressionToString(arg.Value))) + params = append(params, fmt.Sprintf("%s = %s", arg.Name, diffExpressionToString(ctx, arg.Value))) } paramStr := strings.Join(params, ", ") if s.OutputVariable != "" { @@ -387,7 +387,7 @@ func (e *Executor) microflowStatementToMDL(stmt ast.MicroflowStatement, indent i // ============================================================================ // entityToMDL converts a project entity to MDL text -func (e *Executor) entityToMDL(moduleName string, entity *domainmodel.Entity, dm *domainmodel.DomainModel) string { +func entityToMDL(ctx *ExecContext, moduleName string, entity *domainmodel.Entity, dm *domainmodel.DomainModel) string { var lines []string // Documentation @@ -503,7 +503,7 @@ func (e *Executor) entityToMDL(moduleName string, entity *domainmodel.Entity, dm } // viewEntityFromProjectToMDL converts a view entity from project to MDL -func (e *Executor) viewEntityFromProjectToMDL(moduleName string, entity *domainmodel.Entity, dm *domainmodel.DomainModel) string { +func viewEntityFromProjectToMDL(ctx *ExecContext, moduleName string, entity *domainmodel.Entity, dm *domainmodel.DomainModel) string { var lines []string if entity.Documentation != "" { @@ -537,7 +537,7 @@ func (e *Executor) viewEntityFromProjectToMDL(moduleName string, entity *domainm } // enumerationToMDL converts a project enumeration to MDL text -func (e *Executor) enumerationToMDL(moduleName string, enum *model.Enumeration) string { +func enumerationToMDL(ctx *ExecContext, moduleName string, enum *model.Enumeration) string { var lines []string if enum.Documentation != "" { @@ -567,7 +567,7 @@ func (e *Executor) enumerationToMDL(moduleName string, enum *model.Enumeration) } // associationToMDL converts a project association to MDL text -func (e *Executor) associationToMDL(moduleName string, assoc *domainmodel.Association, dm *domainmodel.DomainModel) string { +func associationToMDL(ctx *ExecContext, moduleName string, assoc *domainmodel.Association, dm *domainmodel.DomainModel) string { var lines []string // Build entity name map @@ -620,7 +620,7 @@ func (e *Executor) associationToMDL(moduleName string, assoc *domainmodel.Associ // ============================================================================ // dataTypeToString converts a DataType to its string representation -func (e *Executor) dataTypeToString(dt ast.DataType) string { +func dataTypeToString(_ *ExecContext, dt ast.DataType) string { switch dt.Kind { case ast.TypeString: if dt.Length > 0 { @@ -665,8 +665,8 @@ func (e *Executor) dataTypeToString(dt ast.DataType) string { } } -// expressionToString converts an expression to its string representation -func (e *Executor) expressionToString(expr ast.Expression) string { +// diffExpressionToString converts an expression to its string representation for diff output +func diffExpressionToString(ctx *ExecContext, expr ast.Expression) string { if expr == nil { return "empty" } @@ -688,19 +688,19 @@ func (e *Executor) expressionToString(expr ast.Expression) string { case *ast.AttributePathExpr: return "$" + ex.Variable + "/" + strings.Join(ex.Path, "/") case *ast.BinaryExpr: - return fmt.Sprintf("%s %s %s", e.expressionToString(ex.Left), ex.Operator, e.expressionToString(ex.Right)) + return fmt.Sprintf("%s %s %s", diffExpressionToString(ctx, ex.Left), ex.Operator, diffExpressionToString(ctx, ex.Right)) case *ast.UnaryExpr: - return fmt.Sprintf("%s%s", ex.Operator, e.expressionToString(ex.Operand)) + return fmt.Sprintf("%s%s", ex.Operator, diffExpressionToString(ctx, ex.Operand)) case *ast.FunctionCallExpr: var args []string for _, arg := range ex.Arguments { - args = append(args, e.expressionToString(arg)) + args = append(args, diffExpressionToString(ctx, arg)) } return fmt.Sprintf("%s(%s)", ex.Name, strings.Join(args, ", ")) case *ast.TokenExpr: return fmt.Sprintf("[%%%s%%]", ex.Token) case *ast.ParenExpr: - return fmt.Sprintf("(%s)", e.expressionToString(ex.Inner)) + return fmt.Sprintf("(%s)", diffExpressionToString(ctx, ex.Inner)) case *ast.QualifiedNameExpr: return ex.QualifiedName.String() case *ast.ConstantRefExpr: diff --git a/mdl/executor/cmd_diff_output.go b/mdl/executor/cmd_diff_output.go index b0c37959..f5974b7a 100644 --- a/mdl/executor/cmd_diff_output.go +++ b/mdl/executor/cmd_diff_output.go @@ -15,7 +15,7 @@ import ( // ============================================================================ // outputUnifiedDiff outputs diff in unified format -func (e *Executor) outputUnifiedDiff(result DiffResult, useColor bool) { +func outputUnifiedDiff(ctx *ExecContext, result DiffResult, useColor bool) { if result.IsNew { // New object - show all as additions header := fmt.Sprintf("--- /dev/null\n+++ %s.%s (new)\n", @@ -23,16 +23,16 @@ func (e *Executor) outputUnifiedDiff(result DiffResult, useColor bool) { if useColor { header = colorCyan + header + colorReset } - fmt.Fprint(e.output, header) + fmt.Fprint(ctx.Output, header) for line := range strings.SplitSeq(result.Proposed, "\n") { if useColor { - fmt.Fprintf(e.output, "%s+%s%s\n", colorGreen, line, colorReset) + fmt.Fprintf(ctx.Output, "%s+%s%s\n", colorGreen, line, colorReset) } else { - fmt.Fprintf(e.output, "+%s\n", line) + fmt.Fprintf(ctx.Output, "+%s\n", line) } } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) return } @@ -55,23 +55,23 @@ func (e *Executor) outputUnifiedDiff(result DiffResult, useColor bool) { lines := strings.SplitSeq(text, "\n") for line := range lines { if strings.HasPrefix(line, "+++") || strings.HasPrefix(line, "---") || strings.HasPrefix(line, "@@") { - fmt.Fprintf(e.output, "%s%s%s\n", colorCyan, line, colorReset) + fmt.Fprintf(ctx.Output, "%s%s%s\n", colorCyan, line, colorReset) } else if strings.HasPrefix(line, "+") { - fmt.Fprintf(e.output, "%s%s%s\n", colorGreen, line, colorReset) + fmt.Fprintf(ctx.Output, "%s%s%s\n", colorGreen, line, colorReset) } else if strings.HasPrefix(line, "-") { - fmt.Fprintf(e.output, "%s%s%s\n", colorRed, line, colorReset) + fmt.Fprintf(ctx.Output, "%s%s%s\n", colorRed, line, colorReset) } else { - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } } } else { - fmt.Fprint(e.output, text) + fmt.Fprint(ctx.Output, text) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } // outputSideBySideDiff outputs diff in side-by-side format -func (e *Executor) outputSideBySideDiff(result DiffResult, width int, useColor bool) { +func outputSideBySideDiff(ctx *ExecContext, result DiffResult, width int, useColor bool) { colWidth := (width - 3) / 2 // 3 for separator " | " // Header @@ -79,16 +79,16 @@ func (e *Executor) outputSideBySideDiff(result DiffResult, width int, useColor b if useColor { header = colorCyan + header + colorReset } - fmt.Fprintln(e.output, header) - fmt.Fprintln(e.output, strings.Repeat("─", width)) + fmt.Fprintln(ctx.Output, header) + fmt.Fprintln(ctx.Output, strings.Repeat("─", width)) leftHeader := "Current" rightHeader := "Script" if result.IsNew { leftHeader = "(new)" } - fmt.Fprintf(e.output, "%-*s │ %s\n", colWidth, leftHeader, rightHeader) - fmt.Fprintln(e.output, strings.Repeat("─", width)) + fmt.Fprintf(ctx.Output, "%-*s │ %s\n", colWidth, leftHeader, rightHeader) + fmt.Fprintln(ctx.Output, strings.Repeat("─", width)) currentLines := strings.Split(result.Current, "\n") proposedLines := strings.Split(result.Proposed, "\n") @@ -129,33 +129,33 @@ func (e *Executor) outputSideBySideDiff(result DiffResult, width int, useColor b } } - fmt.Fprintf(e.output, "%-*s │ %s %s\n", colWidth, left, right, marker) + fmt.Fprintf(ctx.Output, "%-*s │ %s %s\n", colWidth, left, right, marker) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } // outputStructuralDiff outputs diff in structural format -func (e *Executor) outputStructuralDiff(result DiffResult, useColor bool) { +func outputStructuralDiff(ctx *ExecContext, result DiffResult, useColor bool) { header := fmt.Sprintf("%s: %s", result.ObjectType, result.ObjectName) if useColor { header = colorCyan + header + colorReset } - fmt.Fprintln(e.output, header) + fmt.Fprintln(ctx.Output, header) if result.IsNew { if useColor { - fmt.Fprintf(e.output, " %s+ New%s\n", colorGreen, colorReset) + fmt.Fprintf(ctx.Output, " %s+ New%s\n", colorGreen, colorReset) } else { - fmt.Fprintln(e.output, " + New") + fmt.Fprintln(ctx.Output, " + New") } } else if result.Current == result.Proposed { - fmt.Fprintln(e.output, " (no changes)") + fmt.Fprintln(ctx.Output, " (no changes)") } else if len(result.Changes) == 0 { // Modified but no specific changes detected - show generic message if useColor { - fmt.Fprintf(e.output, " %s~ Modified%s\n", colorYellow, colorReset) + fmt.Fprintf(ctx.Output, " %s~ Modified%s\n", colorYellow, colorReset) } else { - fmt.Fprintln(e.output, " ~ Modified") + fmt.Fprintln(ctx.Output, " ~ Modified") } } @@ -178,9 +178,9 @@ func (e *Executor) outputStructuralDiff(result DiffResult, useColor bool) { line = fmt.Sprintf(" %s%s %s %s%s%s", colorYellow, marker, change.ElementType, change.ElementName, details, colorReset) } } - fmt.Fprintln(e.output, line) + fmt.Fprintln(ctx.Output, line) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } // truncateLine truncates a string to the given width for diff display diff --git a/mdl/executor/cmd_features.go b/mdl/executor/cmd_features.go index 2b804b8e..2226ff05 100644 --- a/mdl/executor/cmd_features.go +++ b/mdl/executor/cmd_features.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -14,7 +15,9 @@ import ( // checkFeature verifies that a feature is available in the connected project's // version. Returns nil if available, or an actionable error with the version // requirement and a hint. Safe to call when e.reader is nil (returns nil). -func (e *Executor) checkFeature(area, name, statement, hint string) error { +func checkFeature(ctx *ExecContext, area, name, statement, hint string) error { + e := ctx.executor + if e.reader == nil { return nil // No project connected; skip check } @@ -45,9 +48,16 @@ func (e *Executor) checkFeature(area, name, statement, hint string) error { return mdlerrors.NewUnsupported(msg) } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) checkFeature(area, name, statement, hint string) error { + return checkFeature(e.newExecContext(context.Background()), area, name, statement, hint) +} + // execShowFeatures handles SHOW FEATURES, SHOW FEATURES FOR VERSION, and // SHOW FEATURES ADDED SINCE commands. -func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { +func execShowFeatures(ctx *ExecContext, s *ast.ShowFeaturesStmt) error { + e := ctx.executor + reg, err := versions.Load() if err != nil { return mdlerrors.NewBackend("load version registry", err) @@ -63,7 +73,7 @@ func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { if err != nil { return mdlerrors.NewValidationf("invalid version %q: %v", s.AddedSince, err) } - return e.showFeaturesAddedSince(reg, sinceV) + return showFeaturesAddedSince(ctx, reg, sinceV) case s.ForVersion != "": // SHOW FEATURES FOR VERSION x.y — no project connection needed @@ -82,19 +92,26 @@ func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { } if s.InArea != "" { - return e.showFeaturesInArea(reg, pv, s.InArea) + return showFeaturesInArea(ctx, reg, pv, s.InArea) } - return e.showFeaturesAll(reg, pv) + return showFeaturesAll(ctx, reg, pv) } -func (e *Executor) showFeaturesAll(reg *versions.Registry, pv versions.SemVer) error { +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { + return execShowFeatures(e.newExecContext(context.Background()), s) +} + +func showFeaturesAll(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer) error { + e := ctx.executor + features := reg.FeaturesForVersion(pv) if len(features) == 0 { - fmt.Fprintf(e.output, "No features found for version %s\n", pv) + fmt.Fprintf(ctx.Output, "No features found for version %s\n", pv) return nil } - fmt.Fprintf(e.output, "Features for Mendix %s:\n\n", pv) + fmt.Fprintf(ctx.Output, "Features for Mendix %s:\n\n", pv) available, unavailable := 0, 0 tr := &TableResult{ @@ -121,17 +138,19 @@ func (e *Executor) showFeaturesAll(reg *versions.Registry, pv versions.SemVer) e return e.writeResult(tr) } -func (e *Executor) showFeaturesInArea(reg *versions.Registry, pv versions.SemVer, area string) error { +func showFeaturesInArea(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer, area string) error { + e := ctx.executor + features := reg.FeaturesInArea(area, pv) if len(features) == 0 { // Check if the area exists at all. areas := reg.Areas() - fmt.Fprintf(e.output, "No features found in area %q for version %s\n", area, pv) - fmt.Fprintf(e.output, "Available areas: %s\n", strings.Join(areas, ", ")) + fmt.Fprintf(ctx.Output, "No features found in area %q for version %s\n", area, pv) + fmt.Fprintf(ctx.Output, "Available areas: %s\n", strings.Join(areas, ", ")) return nil } - fmt.Fprintf(e.output, "Features in %s for Mendix %s:\n\n", area, pv) + fmt.Fprintf(ctx.Output, "Features in %s for Mendix %s:\n\n", area, pv) tr := &TableResult{ Columns: []string{"Feature", "Available", "Since", "Notes"}, @@ -153,14 +172,16 @@ func (e *Executor) showFeaturesInArea(reg *versions.Registry, pv versions.SemVer return e.writeResult(tr) } -func (e *Executor) showFeaturesAddedSince(reg *versions.Registry, sinceV versions.SemVer) error { +func showFeaturesAddedSince(ctx *ExecContext, reg *versions.Registry, sinceV versions.SemVer) error { + e := ctx.executor + added := reg.FeaturesAddedSince(sinceV) if len(added) == 0 { - fmt.Fprintf(e.output, "No new features found since %s\n", sinceV) + fmt.Fprintf(ctx.Output, "No new features found since %s\n", sinceV) return nil } - fmt.Fprintf(e.output, "Features added since Mendix %s:\n\n", sinceV) + fmt.Fprintf(ctx.Output, "Features added since Mendix %s:\n\n", sinceV) tr := &TableResult{ Columns: []string{"Feature", "Area", "Since", "Notes"}, diff --git a/mdl/executor/cmd_folders.go b/mdl/executor/cmd_folders.go index 0b7c04b0..f9442d8b 100644 --- a/mdl/executor/cmd_folders.go +++ b/mdl/executor/cmd_folders.go @@ -14,7 +14,7 @@ import ( ) // findFolderByPath walks a folder path under a module and returns the folder ID. -func (e *Executor) findFolderByPath(moduleID model.ID, folderPath string, folders []*mpr.FolderInfo) (model.ID, error) { +func findFolderByPath(ctx *ExecContext, moduleID model.ID, folderPath string, folders []*mpr.FolderInfo) (model.ID, error) { parts := strings.Split(folderPath, "/") currentContainerID := moduleID @@ -50,7 +50,8 @@ func (e *Executor) findFolderByPath(moduleID model.ID, folderPath string, folder // execDropFolder handles DROP FOLDER 'path' IN Module statements. // The folder must be empty (no child documents or sub-folders). -func (e *Executor) execDropFolder(s *ast.DropFolderStmt) error { +func execDropFolder(ctx *ExecContext, s *ast.DropFolderStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnected() } @@ -65,7 +66,7 @@ func (e *Executor) execDropFolder(s *ast.DropFolderStmt) error { return mdlerrors.NewBackend("list folders", err) } - folderID, err := e.findFolderByPath(module.ID, s.FolderPath, folders) + folderID, err := findFolderByPath(ctx, module.ID, s.FolderPath, folders) if err != nil { return fmt.Errorf("%w in %s", err, s.Module) } @@ -75,12 +76,13 @@ func (e *Executor) execDropFolder(s *ast.DropFolderStmt) error { } e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped folder: '%s' in %s\n", s.FolderPath, s.Module) + fmt.Fprintf(ctx.Output, "Dropped folder: '%s' in %s\n", s.FolderPath, s.Module) return nil } // execMoveFolder handles MOVE FOLDER Module.FolderName TO ... statements. -func (e *Executor) execMoveFolder(s *ast.MoveFolderStmt) error { +func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnected() } @@ -97,7 +99,7 @@ func (e *Executor) execMoveFolder(s *ast.MoveFolderStmt) error { return mdlerrors.NewBackend("list folders", err) } - folderID, err := e.findFolderByPath(sourceModule.ID, s.Name.Name, folders) + folderID, err := findFolderByPath(ctx, sourceModule.ID, s.Name.Name, folders) if err != nil { return fmt.Errorf("%w in %s", err, s.Name.Module) } @@ -135,6 +137,6 @@ func (e *Executor) execMoveFolder(s *ast.MoveFolderStmt) error { if s.TargetFolder != "" { target += "/" + s.TargetFolder } - fmt.Fprintf(e.output, "Moved folder %s to %s\n", s.Name.String(), target) + fmt.Fprintf(ctx.Output, "Moved folder %s to %s\n", s.Name.String(), target) return nil } diff --git a/mdl/executor/cmd_fragments.go b/mdl/executor/cmd_fragments.go index 62441387..7b6ead8d 100644 --- a/mdl/executor/cmd_fragments.go +++ b/mdl/executor/cmd_fragments.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "io" "sort" @@ -14,62 +15,80 @@ import ( ) // execDefineFragment stores a fragment definition in the executor's session state. -func (e *Executor) execDefineFragment(s *ast.DefineFragmentStmt) error { - if e.fragments == nil { - e.fragments = make(map[string]*ast.DefineFragmentStmt) +func execDefineFragment(ctx *ExecContext, s *ast.DefineFragmentStmt) error { + if ctx.Fragments == nil { + ctx.Fragments = make(map[string]*ast.DefineFragmentStmt) + // Also update the executor's fragments map so newExecContext picks it up. + ctx.executor.fragments = ctx.Fragments } - if _, exists := e.fragments[s.Name]; exists { + if _, exists := ctx.Fragments[s.Name]; exists { return mdlerrors.NewAlreadyExists("fragment", s.Name) } - e.fragments[s.Name] = s - fmt.Fprintf(e.output, "Defined fragment %s (%d widgets)\n", s.Name, len(s.Widgets)) + ctx.Fragments[s.Name] = s + fmt.Fprintf(ctx.Output, "Defined fragment %s (%d widgets)\n", s.Name, len(s.Widgets)) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execDefineFragment(s *ast.DefineFragmentStmt) error { + return execDefineFragment(e.newExecContext(context.Background()), s) +} + // showFragments lists all defined fragments in the current session. -func (e *Executor) showFragments() error { - if len(e.fragments) == 0 { - fmt.Fprintln(e.output, "No fragments defined.") +func showFragments(ctx *ExecContext) error { + if len(ctx.Fragments) == 0 { + fmt.Fprintln(ctx.Output, "No fragments defined.") return nil } // Sort by name for consistent output - names := make([]string, 0, len(e.fragments)) - for name := range e.fragments { + names := make([]string, 0, len(ctx.Fragments)) + for name := range ctx.Fragments { names = append(names, name) } sort.Strings(names) - fmt.Fprintf(e.output, "%-30s %s\n", "Fragment", "Widgets") - fmt.Fprintf(e.output, "%-30s %s\n", strings.Repeat("-", 30), strings.Repeat("-", 10)) + fmt.Fprintf(ctx.Output, "%-30s %s\n", "Fragment", "Widgets") + fmt.Fprintf(ctx.Output, "%-30s %s\n", strings.Repeat("-", 30), strings.Repeat("-", 10)) for _, name := range names { - frag := e.fragments[name] - fmt.Fprintf(e.output, "%-30s %d\n", name, len(frag.Widgets)) + frag := ctx.Fragments[name] + fmt.Fprintf(ctx.Output, "%-30s %d\n", name, len(frag.Widgets)) } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) showFragments() error { + return showFragments(e.newExecContext(context.Background())) +} + // describeFragment outputs a fragment's definition as MDL. -func (e *Executor) describeFragment(name ast.QualifiedName) error { - if e.fragments == nil { +func describeFragment(ctx *ExecContext, name ast.QualifiedName) error { + if ctx.Fragments == nil { return mdlerrors.NewNotFound("fragment", name.Name) } - frag, ok := e.fragments[name.Name] + frag, ok := ctx.Fragments[name.Name] if !ok { return mdlerrors.NewNotFound("fragment", name.Name) } - fmt.Fprintf(e.output, "DEFINE FRAGMENT %s AS {\n", frag.Name) + fmt.Fprintf(ctx.Output, "DEFINE FRAGMENT %s AS {\n", frag.Name) for _, w := range frag.Widgets { - outputASTWidgetMDL(e.output, w, 1) + outputASTWidgetMDL(ctx.Output, w, 1) } - fmt.Fprintln(e.output, "};") + fmt.Fprintln(ctx.Output, "};") return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeFragment(name ast.QualifiedName) error { + return describeFragment(e.newExecContext(context.Background()), name) +} + // describeFragmentFrom handles DESCRIBE FRAGMENT FROM PAGE/SNIPPET ... WIDGET ... command. // It finds a named widget in a page or snippet and outputs it as MDL. -func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { +func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -132,6 +151,11 @@ func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { + return describeFragmentFrom(e.newExecContext(context.Background()), s) +} + // findRawWidgetByName recursively searches the widget tree for a widget with the given name. func findRawWidgetByName(widgets []rawWidget, name string) *rawWidget { for i := range widgets { diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index c4ef26fd..1b84c7d3 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -257,7 +257,7 @@ func (e *Executor) showImageCollections(moduleName string) error { } // findImageCollection finds an image collection by module and name. -func (e *Executor) findImageCollection(moduleName, collectionName string) *mpr.ImageCollection { +func findImageCollection(e *Executor, moduleName, collectionName string) *mpr.ImageCollection { collections, err := e.reader.ListImageCollections() if err != nil { return nil @@ -277,3 +277,8 @@ func (e *Executor) findImageCollection(moduleName, collectionName string) *mpr.I } return nil } + +// Executor wrapper for unmigrated callers. +func (e *Executor) findImageCollection(moduleName, collectionName string) *mpr.ImageCollection { + return findImageCollection(e, moduleName, collectionName) +} diff --git a/mdl/executor/cmd_import.go b/mdl/executor/cmd_import.go index 30d4cbf4..8399c938 100644 --- a/mdl/executor/cmd_import.go +++ b/mdl/executor/cmd_import.go @@ -15,7 +15,8 @@ import ( ) // execImport handles IMPORT FROM QUERY '' INTO Module.Entity MAP (...) [LINK (...)] [BATCH n] [LIMIT n] -func (e *Executor) execImport(s *ast.ImportStmt) error { +func execImport(ctx *ExecContext, s *ast.ImportStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -27,13 +28,13 @@ func (e *Executor) execImport(s *ast.ImportStmt) error { } // Get source connection (auto-connects from config if needed) - sourceConn, err := e.getOrAutoConnect(s.SourceAlias) + sourceConn, err := getOrAutoConnect(ctx, s.SourceAlias) if err != nil { return fmt.Errorf("source connection: %w", err) } // Get or create Mendix DB connection - targetConn, err := e.ensureMendixDBConnection() + targetConn, err := ensureMendixDBConnection(ctx) if err != nil { return err } @@ -48,10 +49,10 @@ func (e *Executor) execImport(s *ast.ImportStmt) error { } // Resolve association LINK mappings - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + goCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() - assocs, err := e.resolveImportLinks(ctx, targetConn, s) + assocs, err := resolveImportLinks(ctx, goCtx, targetConn, s) if err != nil { return err } @@ -70,15 +71,15 @@ func (e *Executor) execImport(s *ast.ImportStmt) error { start := time.Now() - result, err := sqllib.ExecuteImport(ctx, cfg, func(batch, rows int) { - fmt.Fprintf(e.output, " batch %d: %d rows imported\n", batch, rows) + result, err := sqllib.ExecuteImport(goCtx, cfg, func(batch, rows int) { + fmt.Fprintf(ctx.Output, " batch %d: %d rows imported\n", batch, rows) }) if err != nil { return mdlerrors.NewBackend("import", err) } elapsed := time.Since(start) - fmt.Fprintf(e.output, "Imported %d rows into %s (%d batches, %s)\n", + fmt.Fprintf(ctx.Output, "Imported %d rows into %s (%d batches, %s)\n", result.TotalRows, s.TargetEntity, result.BatchesWritten, elapsed.Round(time.Millisecond)) // Report association link stats @@ -86,24 +87,30 @@ func (e *Executor) execImport(s *ast.ImportStmt) error { linked := result.LinksCreated[a.AssociationName] missed := result.LinksMissed[a.AssociationName] if missed > 0 { - fmt.Fprintf(e.output, " %s: linked %d/%d rows (%d NULL — lookup value not found)\n", + fmt.Fprintf(ctx.Output, " %s: linked %d/%d rows (%d NULL — lookup value not found)\n", a.AssociationName, linked, linked+missed, missed) } else if linked > 0 { - fmt.Fprintf(e.output, " %s: linked %d rows\n", a.AssociationName, linked) + fmt.Fprintf(ctx.Output, " %s: linked %d rows\n", a.AssociationName, linked) } } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execImport(s *ast.ImportStmt) error { + return execImport(e.newExecContext(context.Background()), s) +} + // resolveImportLinks resolves LINK mappings from the AST into AssocInfo structs // by looking up association metadata from the MPR and the Mendix system tables. -func (e *Executor) resolveImportLinks(ctx context.Context, mendixConn *sqllib.Connection, s *ast.ImportStmt) ([]*sqllib.AssocInfo, error) { +func resolveImportLinks(ctx *ExecContext, goCtx context.Context, mendixConn *sqllib.Connection, s *ast.ImportStmt) ([]*sqllib.AssocInfo, error) { + e := ctx.executor if len(s.Links) == 0 { return nil, nil } - fmt.Fprintf(e.output, "Resolving associations...\n") + fmt.Fprintf(ctx.Output, "Resolving associations...\n") // Parse target entity module targetParts := strings.SplitN(s.TargetEntity, ".", 2) @@ -136,18 +143,18 @@ func (e *Executor) resolveImportLinks(ctx context.Context, mendixConn *sqllib.Co // Resolve each LINK mapping var assocs []*sqllib.AssocInfo for _, link := range s.Links { - info, err := e.resolveOneLink(ctx, mendixConn, link, targetModule, dms, h, entityNames) + info, err := resolveOneLink(ctx, goCtx, mendixConn, link, targetModule, dms, h, entityNames) if err != nil { return nil, err } - fmt.Fprintf(e.output, " %s: %s storage", info.AssociationName, info.StorageFormat) + fmt.Fprintf(ctx.Output, " %s: %s storage", info.AssociationName, info.StorageFormat) if info.LookupAttr != "" { - fmt.Fprintf(e.output, ", lookup by %s.%s (%d values cached)", + fmt.Fprintf(ctx.Output, ", lookup by %s.%s (%d values cached)", info.ChildEntity, info.LookupAttr, len(info.LookupCache)) } else { - fmt.Fprintf(e.output, ", direct ID") + fmt.Fprintf(ctx.Output, ", direct ID") } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) assocs = append(assocs, info) } @@ -155,8 +162,9 @@ func (e *Executor) resolveImportLinks(ctx context.Context, mendixConn *sqllib.Co } // resolveOneLink resolves a single LINK mapping to an AssocInfo. -func (e *Executor) resolveOneLink( - ctx context.Context, +func resolveOneLink( + ctx *ExecContext, + goCtx context.Context, mendixConn *sqllib.Connection, link ast.LinkMapping, targetModule string, @@ -239,7 +247,7 @@ func (e *Executor) resolveOneLink( } // Try to get exact column/table names from mendixsystem$association - sysInfo, err := sqllib.LookupAssociationInfo(ctx, mendixConn, assocQualName) + sysInfo, err := sqllib.LookupAssociationInfo(goCtx, mendixConn, assocQualName) if err != nil { return nil, err } @@ -282,13 +290,13 @@ func (e *Executor) resolveOneLink( return nil, fmt.Errorf("invalid child entity %q: %w", childEntity, err) } lookupCol := sqllib.AttributeToColumnName(link.LookupAttr) - cache, err := sqllib.BuildLookupCache(ctx, mendixConn, childTable, lookupCol) + cache, err := sqllib.BuildLookupCache(goCtx, mendixConn, childTable, lookupCol) if err != nil { return nil, mdlerrors.NewBackend(fmt.Sprintf("build lookup cache for %s", assocQualName), err) } info.LookupCache = cache if len(cache) == 0 { - fmt.Fprintf(e.output, " WARNING: child table %q is empty; all %s associations will be NULL\n", + fmt.Fprintf(ctx.Output, " WARNING: child table %q is empty; all %s associations will be NULL\n", childTable, assocQualName) } } @@ -314,8 +322,9 @@ func getParentEntityName(a *domainmodel.Association, ca *domainmodel.CrossModule } // ensureMendixDBConnection reads the project settings and auto-connects to the Mendix app DB. -func (e *Executor) ensureMendixDBConnection() (*sqllib.Connection, error) { - mgr := e.ensureSQLManager() +func ensureMendixDBConnection(ctx *ExecContext) (*sqllib.Connection, error) { + e := ctx.executor + mgr := ensureSQLManager(ctx) // Check if already connected if conn, err := mgr.Get(sqllib.MendixDBAlias); err == nil { @@ -345,7 +354,7 @@ func (e *Executor) ensureMendixDBConnection() (*sqllib.Connection, error) { return nil, mdlerrors.NewBackend("connect to Mendix app database", err) } - fmt.Fprintf(e.output, "Auto-connected to Mendix app database as '%s'\n", sqllib.MendixDBAlias) + fmt.Fprintf(ctx.Output, "Auto-connected to Mendix app database as '%s'\n", sqllib.MendixDBAlias) conn, err := mgr.Get(sqllib.MendixDBAlias) if err != nil { diff --git a/mdl/executor/cmd_languages.go b/mdl/executor/cmd_languages.go index b1144879..5dc602a8 100644 --- a/mdl/executor/cmd_languages.go +++ b/mdl/executor/cmd_languages.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" mdlerrors "github.com/mendixlabs/mxcli/mdl/errors" @@ -10,12 +11,13 @@ import ( // showLanguages lists all languages found in the project's translatable strings. // Requires REFRESH CATALOG FULL to populate the strings table. -func (e *Executor) showLanguages() error { - if e.catalog == nil { +func showLanguages(ctx *ExecContext) error { + e := ctx.executor + if ctx.Catalog == nil { return mdlerrors.NewValidation("no catalog available — run REFRESH CATALOG FULL first") } - result, err := e.catalog.Query(` + result, err := ctx.Catalog.Query(` SELECT Language, COUNT(*) as StringCount FROM strings WHERE Language != '' @@ -27,7 +29,7 @@ func (e *Executor) showLanguages() error { } if len(result.Rows) == 0 { - fmt.Fprintln(e.output, "No translatable strings found. Run REFRESH CATALOG FULL to populate the strings table.") + fmt.Fprintln(ctx.Output, "No translatable strings found. Run REFRESH CATALOG FULL to populate the strings table.") return nil } @@ -48,3 +50,9 @@ func (e *Executor) showLanguages() error { } return e.writeResult(tr) } + +// --- Executor method wrapper for backward compatibility --- + +func (e *Executor) showLanguages() error { + return showLanguages(e.newExecContext(context.Background())) +} diff --git a/mdl/executor/cmd_lint.go b/mdl/executor/cmd_lint.go index eb36bd29..8212806c 100644 --- a/mdl/executor/cmd_lint.go +++ b/mdl/executor/cmd_lint.go @@ -14,44 +14,45 @@ import ( ) // execLint executes a LINT statement. -func (e *Executor) execLint(s *ast.LintStmt) error { +func execLint(ctx *ExecContext, s *ast.LintStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } // Handle SHOW LINT RULES if s.ShowRules { - return e.showLintRules() + return showLintRules(ctx) } // Ensure catalog is built - if e.catalog == nil { - fmt.Fprintln(e.output, "Building catalog for linting...") + if ctx.Catalog == nil { + fmt.Fprintln(ctx.Output, "Building catalog for linting...") if err := e.buildCatalog(false); err != nil { return mdlerrors.NewBackend("build catalog", err) } } // Create lint context - ctx := linter.NewLintContext(e.catalog) - ctx.SetReader(e.reader) + lintCtx := linter.NewLintContext(ctx.Catalog) + lintCtx.SetReader(e.reader) // Load configuration projectDir := filepath.Dir(e.mprPath) configPath := linter.FindConfigFile(projectDir) config, err := linter.LoadConfig(configPath) if err != nil { - fmt.Fprintf(e.output, "Warning: failed to load lint config: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: failed to load lint config: %v\n", err) config = linter.DefaultConfig() } // Set excluded modules from config if len(config.ExcludeModules) > 0 { - ctx.SetExcludedModules(config.ExcludeModules) + lintCtx.SetExcludedModules(config.ExcludeModules) } // Create linter and register built-in rules - lint := linter.New(ctx) + lint := linter.New(lintCtx) lint.AddRule(rules.NewNamingConventionRule()) lint.AddRule(rules.NewEmptyMicroflowRule()) lint.AddRule(rules.NewDomainModelSizeRule()) @@ -63,7 +64,7 @@ func (e *Executor) execLint(s *ast.LintStmt) error { rulesDir := filepath.Join(projectDir, ".claude", "lint-rules") starlarkRules, err := linter.LoadStarlarkRulesFromDir(rulesDir) if err != nil { - fmt.Fprintf(e.output, "Warning: failed to load custom rules: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: failed to load custom rules: %v\n", err) } for _, rule := range starlarkRules { lint.AddRule(rule) @@ -75,9 +76,9 @@ func (e *Executor) execLint(s *ast.LintStmt) error { // Handle module filtering if s.Target != nil && s.ModuleOnly { // Only lint specific module - set all others as excluded - ctx.SetExcludedModules(nil) // Clear any existing exclusions + lintCtx.SetExcludedModules(nil) // Clear any existing exclusions // This is a simplified approach - ideally we'd filter in the linter - fmt.Fprintf(e.output, "Linting module: %s\n", s.Target.Module) + fmt.Fprintf(ctx.Output, "Linting module: %s\n", s.Target.Module) } // Run linting @@ -109,13 +110,14 @@ func (e *Executor) execLint(s *ast.LintStmt) error { } formatter := linter.GetFormatter(format, false) - return formatter.Format(violations, e.output) + return formatter.Format(violations, ctx.Output) } // showLintRules displays available lint rules. -func (e *Executor) showLintRules() error { - fmt.Fprintln(e.output, "Built-in rules:") - fmt.Fprintln(e.output) +func showLintRules(ctx *ExecContext) error { + e := ctx.executor + fmt.Fprintln(ctx.Output, "Built-in rules:") + fmt.Fprintln(ctx.Output) // Create a temporary linter with built-in rules lint := linter.New(nil) @@ -127,10 +129,10 @@ func (e *Executor) showLintRules() error { lint.AddRule(rules.NewMissingTranslationsRule()) for _, rule := range lint.Rules() { - fmt.Fprintf(e.output, " %s (%s)\n", rule.ID(), rule.Name()) - fmt.Fprintf(e.output, " %s\n", rule.Description()) - fmt.Fprintf(e.output, " Category: %s, Default Severity: %s\n", rule.Category(), rule.DefaultSeverity()) - fmt.Fprintln(e.output) + fmt.Fprintf(ctx.Output, " %s (%s)\n", rule.ID(), rule.Name()) + fmt.Fprintf(ctx.Output, " %s\n", rule.Description()) + fmt.Fprintf(ctx.Output, " Category: %s, Default Severity: %s\n", rule.Category(), rule.DefaultSeverity()) + fmt.Fprintln(ctx.Output) } // Show custom Starlark rules if connected @@ -139,16 +141,26 @@ func (e *Executor) showLintRules() error { rulesDir := filepath.Join(projectDir, ".claude", "lint-rules") starlarkRules, err := linter.LoadStarlarkRulesFromDir(rulesDir) if err == nil && len(starlarkRules) > 0 { - fmt.Fprintln(e.output, "Custom rules (from .claude/lint-rules/):") - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output, "Custom rules (from .claude/lint-rules/):") + fmt.Fprintln(ctx.Output) for _, rule := range starlarkRules { - fmt.Fprintf(e.output, " %s (%s)\n", rule.ID(), rule.Name()) - fmt.Fprintf(e.output, " %s\n", rule.Description()) - fmt.Fprintf(e.output, " Category: %s, Default Severity: %s\n", rule.Category(), rule.DefaultSeverity()) - fmt.Fprintln(e.output) + fmt.Fprintf(ctx.Output, " %s (%s)\n", rule.ID(), rule.Name()) + fmt.Fprintf(ctx.Output, " %s\n", rule.Description()) + fmt.Fprintf(ctx.Output, " Category: %s, Default Severity: %s\n", rule.Category(), rule.DefaultSeverity()) + fmt.Fprintln(ctx.Output) } } } return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) execLint(s *ast.LintStmt) error { + return execLint(e.newExecContext(context.Background()), s) +} + +func (e *Executor) showLintRules() error { + return showLintRules(e.newExecContext(context.Background())) +} diff --git a/mdl/executor/cmd_mermaid.go b/mdl/executor/cmd_mermaid.go index 6f746ea2..758756d0 100644 --- a/mdl/executor/cmd_mermaid.go +++ b/mdl/executor/cmd_mermaid.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -14,9 +15,10 @@ import ( "github.com/mendixlabs/mxcli/sdk/pages" ) -// DescribeMermaid generates a Mermaid diagram for the given object type and name. +// describeMermaid generates a Mermaid diagram for the given object type and name. // Supported types: entity (renders full domain model), microflow, page. -func (e *Executor) DescribeMermaid(objectType, name string) error { +func describeMermaid(ctx *ExecContext, objectType, name string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -31,18 +33,24 @@ func (e *Executor) DescribeMermaid(objectType, name string) error { switch strings.ToUpper(objectType) { case "ENTITY", "DOMAINMODEL": - return e.domainModelToMermaid(qn.Module) + return domainModelToMermaid(ctx, qn.Module) case "MICROFLOW": - return e.microflowToMermaid(qn) + return microflowToMermaid(ctx, qn) case "PAGE": - return e.pageToMermaid(qn) + return pageToMermaid(ctx, qn) default: return mdlerrors.NewUnsupported(fmt.Sprintf("mermaid format not supported for type: %s", objectType)) } } +// DescribeMermaid is a method wrapper for external callers. +func (e *Executor) DescribeMermaid(objectType, name string) error { + return describeMermaid(e.newExecContext(context.Background()), objectType, name) +} + // domainModelToMermaid generates a Mermaid erDiagram for a module's domain model. -func (e *Executor) domainModelToMermaid(moduleName string) error { +func domainModelToMermaid(ctx *ExecContext, moduleName string) error { + e := ctx.executor module, err := e.findModule(moduleName) if err != nil { return err @@ -176,12 +184,13 @@ func (e *Executor) domainModelToMermaid(moduleName string) error { } sb.WriteString("}\n") - fmt.Fprint(e.output, sb.String()) + fmt.Fprint(ctx.Output, sb.String()) return nil } // microflowToMermaid generates a Mermaid flowchart for a microflow. -func (e *Executor) microflowToMermaid(name ast.QualifiedName) error { +func microflowToMermaid(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -217,17 +226,17 @@ func (e *Executor) microflowToMermaid(name ast.QualifiedName) error { return mdlerrors.NewNotFound("microflow", name.String()) } - return e.renderMicroflowMermaid(targetMf, entityNames) + return renderMicroflowMermaid(ctx, targetMf, entityNames) } // renderMicroflowMermaid renders a microflow as a Mermaid flowchart. -func (e *Executor) renderMicroflowMermaid(mf *microflows.Microflow, entityNames map[model.ID]string) error { +func renderMicroflowMermaid(ctx *ExecContext, mf *microflows.Microflow, entityNames map[model.ID]string) error { var sb strings.Builder sb.WriteString("flowchart LR\n") if mf.ObjectCollection == nil || len(mf.ObjectCollection.Objects) == 0 { sb.WriteString(" start([Start]) --> stop([End])\n") - fmt.Fprint(e.output, sb.String()) + fmt.Fprint(ctx.Output, sb.String()) return nil } @@ -354,12 +363,13 @@ func (e *Executor) renderMicroflowMermaid(mf *microflows.Microflow, entityNames sb.WriteString("}\n") } - fmt.Fprint(e.output, sb.String()) + fmt.Fprint(ctx.Output, sb.String()) return nil } // pageToMermaid generates a Mermaid block diagram for a page's widget structure. -func (e *Executor) pageToMermaid(name ast.QualifiedName) error { +func pageToMermaid(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor h, err := e.getHierarchy() if err != nil { return mdlerrors.NewBackend("build hierarchy", err) @@ -396,17 +406,17 @@ func (e *Executor) pageToMermaid(name ast.QualifiedName) error { sb.WriteString(fmt.Sprintf(" page_title[\"%s\"]\n", sanitizeMermaidLabel(title))) // Render widget tree - e.renderRawWidgetMermaid(&sb, rawWidgets, 1, 0) + renderRawWidgetMermaid(ctx, &sb, rawWidgets, 1, 0) // Emit metadata for the webview sb.WriteString("\n%% @type block\n") - fmt.Fprint(e.output, sb.String()) + fmt.Fprint(ctx.Output, sb.String()) return nil } // renderRawWidgetMermaid recursively renders raw widgets in Mermaid block-beta syntax. -func (e *Executor) renderRawWidgetMermaid(sb *strings.Builder, widgets []rawWidget, depth int, counter int) int { +func renderRawWidgetMermaid(ctx *ExecContext, sb *strings.Builder, widgets []rawWidget, depth int, counter int) int { for _, w := range widgets { counter++ id := fmt.Sprintf("w%d", counter) @@ -418,7 +428,7 @@ func (e *Executor) renderRawWidgetMermaid(sb *strings.Builder, widgets []rawWidg if len(w.Children) > 0 { fmt.Fprintf(sb, "%s%s[\"%s\"]:\n", indent, id, sanitizeMermaidLabel(label)) - counter = e.renderRawWidgetMermaid(sb, w.Children, depth+1, counter) + counter = renderRawWidgetMermaid(ctx, sb, w.Children, depth+1, counter) } else { fmt.Fprintf(sb, "%s%s[\"%s\"]\n", indent, id, sanitizeMermaidLabel(label)) } diff --git a/mdl/executor/cmd_misc.go b/mdl/executor/cmd_misc.go index be003093..66aa4baf 100644 --- a/mdl/executor/cmd_misc.go +++ b/mdl/executor/cmd_misc.go @@ -4,6 +4,7 @@ package executor import ( + "context" "errors" "fmt" "os" @@ -20,31 +21,48 @@ import ( var ErrExit = mdlerrors.ErrExit // execUpdate handles UPDATE statements (refresh from disk). -func (e *Executor) execUpdate() error { +func execUpdate(ctx *ExecContext) error { + e := ctx.executor if e.mprPath == "" { return mdlerrors.NewNotConnected() } // Reconnect to refresh path := e.mprPath - e.execDisconnect() - return e.execConnect(&ast.ConnectStmt{Path: path}) + execDisconnect(ctx) + return execConnect(ctx, &ast.ConnectStmt{Path: path}) +} + +// Executor wrapper for unmigrated callers. +func (e *Executor) execUpdate() error { + return execUpdate(e.newExecContext(context.Background())) } // execRefresh handles REFRESH statements (alias for UPDATE). +func execRefresh(ctx *ExecContext) error { + return execUpdate(ctx) +} + +// Executor wrapper for unmigrated callers. func (e *Executor) execRefresh() error { - return e.execUpdate() + return execRefresh(e.newExecContext(context.Background())) } // execSet handles SET statements. -func (e *Executor) execSet(s *ast.SetStmt) error { +func execSet(ctx *ExecContext, s *ast.SetStmt) error { + e := ctx.executor e.settings[s.Key] = s.Value - fmt.Fprintf(e.output, "Set %s = %v\n", s.Key, s.Value) + fmt.Fprintf(ctx.Output, "Set %s = %v\n", s.Key, s.Value) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execSet(s *ast.SetStmt) error { + return execSet(e.newExecContext(context.Background()), s) +} + // execHelp handles HELP statements. -func (e *Executor) execHelp() error { +func execHelp(ctx *ExecContext) error { help := `MDL Commands: Connection: @@ -317,37 +335,54 @@ Other: Statement Terminator: Use ; or / to end statements ` - fmt.Fprint(e.output, help) + fmt.Fprint(ctx.Output, help) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execHelp() error { + return execHelp(e.newExecContext(context.Background())) +} + // showVersion displays Mendix project version information. -func (e *Executor) showVersion() error { +func showVersion(ctx *ExecContext) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } pv := e.reader.ProjectVersion() - fmt.Fprintf(e.output, "Mendix Version: %s\n", pv.ProductVersion) - fmt.Fprintf(e.output, "Build Version: %s\n", pv.BuildVersion) - fmt.Fprintf(e.output, "MPR Format: v%d\n", pv.FormatVersion) + fmt.Fprintf(ctx.Output, "Mendix Version: %s\n", pv.ProductVersion) + fmt.Fprintf(ctx.Output, "Build Version: %s\n", pv.BuildVersion) + fmt.Fprintf(ctx.Output, "MPR Format: v%d\n", pv.FormatVersion) if pv.SchemaHash != "" { - fmt.Fprintf(e.output, "Schema Hash: %s\n", pv.SchemaHash) + fmt.Fprintf(ctx.Output, "Schema Hash: %s\n", pv.SchemaHash) } return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) showVersion() error { + return showVersion(e.newExecContext(context.Background())) +} + // execExit handles EXIT statements. // Note: This just signals exit intent via ErrExit. The actual cleanup // is done by the caller (CLI/REPL) when they handle ErrExit at the top level. // This allows exit within nested scripts to stop just that script without // closing the database connection. -func (e *Executor) execExit() error { +func execExit(ctx *ExecContext) error { return ErrExit } +// Executor wrapper for unmigrated callers. +func (e *Executor) execExit() error { + return execExit(e.newExecContext(context.Background())) +} + // execExecuteScript handles EXECUTE SCRIPT statements. -func (e *Executor) execExecuteScript(s *ast.ExecuteScriptStmt) error { +func execExecuteScript(ctx *ExecContext, s *ast.ExecuteScriptStmt) error { + e := ctx.executor // Resolve path relative to current working directory scriptPath := s.Path if !filepath.IsAbs(scriptPath) { @@ -372,28 +407,33 @@ func (e *Executor) execExecuteScript(s *ast.ExecuteScriptStmt) error { prog, errs := visitor.Build(processedContent) if len(errs) > 0 { for _, err := range errs { - fmt.Fprintf(e.output, "Parse error in %s: %v\n", s.Path, err) + fmt.Fprintf(ctx.Output, "Parse error in %s: %v\n", s.Path, err) } return mdlerrors.NewValidationf("script '%s' has parse errors", s.Path) } // Execute all statements in the script - fmt.Fprintf(e.output, "Executing script: %s\n", s.Path) + fmt.Fprintf(ctx.Output, "Executing script: %s\n", s.Path) for _, stmt := range prog.Statements { if err := e.Execute(stmt); err != nil { // Exit within a script just stops the script, doesn't exit mxcli if errors.Is(err, ErrExit) { - fmt.Fprintf(e.output, "Script exited: %s\n", s.Path) + fmt.Fprintf(ctx.Output, "Script exited: %s\n", s.Path) return nil } return fmt.Errorf("error in script '%s': %w", s.Path, err) } } - fmt.Fprintf(e.output, "Script completed: %s\n", s.Path) + fmt.Fprintf(ctx.Output, "Script completed: %s\n", s.Path) return nil } +// Executor wrapper for unmigrated callers. +func (e *Executor) execExecuteScript(s *ast.ExecuteScriptStmt) error { + return execExecuteScript(e.newExecContext(context.Background()), s) +} + // stripSlashSeparators removes lines that contain only "/" from the script content. // This allows "/" to be used as a statement separator (SQL*Plus style) without // requiring the grammar to support it. diff --git a/mdl/executor/cmd_module_overview.go b/mdl/executor/cmd_module_overview.go index 31f5e1c0..b67875e2 100644 --- a/mdl/executor/cmd_module_overview.go +++ b/mdl/executor/cmd_module_overview.go @@ -3,6 +3,7 @@ package executor import ( + "context" "encoding/json" "fmt" @@ -46,7 +47,8 @@ var systemModuleNames = map[string]bool{ // ModuleOverview generates a JSON graph of all project modules and their // cross-module dependencies, suitable for rendering with ELK.js. -func (e *Executor) ModuleOverview() error { +func ModuleOverview(ctx *ExecContext) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -57,7 +59,7 @@ func (e *Executor) ModuleOverview() error { } // Get all module names - moduleResult, err := e.catalog.Query("SELECT Name FROM modules") + moduleResult, err := ctx.Catalog.Query("SELECT Name FROM modules") if err != nil { return mdlerrors.NewBackend("query modules", err) } @@ -71,7 +73,7 @@ func (e *Executor) ModuleOverview() error { // Get entity counts per module entityCounts := make(map[string]int) - result, err := e.catalog.Query("SELECT ModuleName, COUNT(*) FROM entities GROUP BY ModuleName") + result, err := ctx.Catalog.Query("SELECT ModuleName, COUNT(*) FROM entities GROUP BY ModuleName") if err == nil { for _, row := range result.Rows { if name, ok := row[0].(string); ok { @@ -82,7 +84,7 @@ func (e *Executor) ModuleOverview() error { // Get microflow counts per module mfCounts := make(map[string]int) - result, err = e.catalog.Query("SELECT ModuleName, COUNT(*) FROM microflows GROUP BY ModuleName") + result, err = ctx.Catalog.Query("SELECT ModuleName, COUNT(*) FROM microflows GROUP BY ModuleName") if err == nil { for _, row := range result.Rows { if name, ok := row[0].(string); ok { @@ -93,7 +95,7 @@ func (e *Executor) ModuleOverview() error { // Get page counts per module pageCounts := make(map[string]int) - result, err = e.catalog.Query("SELECT ModuleName, COUNT(*) FROM pages GROUP BY ModuleName") + result, err = ctx.Catalog.Query("SELECT ModuleName, COUNT(*) FROM pages GROUP BY ModuleName") if err == nil { for _, row := range result.Rows { if name, ok := row[0].(string); ok { @@ -119,7 +121,7 @@ func (e *Executor) ModuleOverview() error { sortModuleNodes(modules) // Query cross-module dependency edges from REFS - edgeResult, err := e.catalog.Query(` + edgeResult, err := ctx.Catalog.Query(` SELECT SUBSTR(SourceName, 1, INSTR(SourceName, '.') - 1) as SourceModule, SUBSTR(TargetName, 1, INSTR(TargetName, '.') - 1) as TargetModule, @@ -186,7 +188,7 @@ func (e *Executor) ModuleOverview() error { return mdlerrors.NewBackend("marshal JSON", err) } - fmt.Fprint(e.output, string(out)) + fmt.Fprint(ctx.Output, string(out)) return nil } @@ -226,3 +228,9 @@ func sortModuleEdges(edges []moduleOverviewEdge) { } } } + +// --- Executor method wrapper for backward compatibility --- + +func (e *Executor) ModuleOverview() error { + return ModuleOverview(e.newExecContext(context.Background())) +} diff --git a/mdl/executor/cmd_move.go b/mdl/executor/cmd_move.go index b531c2fe..2baf8790 100644 --- a/mdl/executor/cmd_move.go +++ b/mdl/executor/cmd_move.go @@ -13,7 +13,8 @@ import ( ) // execMove handles MOVE PAGE/MICROFLOW/SNIPPET/NANOFLOW/ENTITY/ENUMERATION statements. -func (e *Executor) execMove(s *ast.MoveStmt) error { +func execMove(ctx *ExecContext, s *ast.MoveStmt) error { + e := ctx.executor if e.writer == nil { return mdlerrors.NewNotConnected() } @@ -39,7 +40,7 @@ func (e *Executor) execMove(s *ast.MoveStmt) error { // Entity moves are handled specially (entities are embedded in domain models, not top-level units) if s.DocumentType == ast.DocumentTypeEntity { - return e.moveEntity(s.Name, sourceModule, targetModule) + return moveEntity(ctx, s.Name, sourceModule, targetModule) } // Resolve target container (folder or module root) @@ -56,29 +57,29 @@ func (e *Executor) execMove(s *ast.MoveStmt) error { // Execute move based on document type switch s.DocumentType { case ast.DocumentTypePage: - if err := e.movePage(s.Name, targetContainerID); err != nil { + if err := movePage(ctx, s.Name, targetContainerID); err != nil { return err } case ast.DocumentTypeMicroflow: - if err := e.moveMicroflow(s.Name, targetContainerID); err != nil { + if err := moveMicroflow(ctx, s.Name, targetContainerID); err != nil { return err } case ast.DocumentTypeSnippet: - if err := e.moveSnippet(s.Name, targetContainerID); err != nil { + if err := moveSnippet(ctx, s.Name, targetContainerID); err != nil { return err } case ast.DocumentTypeNanoflow: - if err := e.moveNanoflow(s.Name, targetContainerID); err != nil { + if err := moveNanoflow(ctx, s.Name, targetContainerID); err != nil { return err } case ast.DocumentTypeEnumeration: - return e.moveEnumeration(s.Name, targetContainerID, targetModule.Name) + return moveEnumeration(ctx, s.Name, targetContainerID, targetModule.Name) case ast.DocumentTypeConstant: - if err := e.moveConstant(s.Name, targetContainerID); err != nil { + if err := moveConstant(ctx, s.Name, targetContainerID); err != nil { return err } case ast.DocumentTypeDatabaseConnection: - if err := e.moveDatabaseConnection(s.Name, targetContainerID); err != nil { + if err := moveDatabaseConnection(ctx, s.Name, targetContainerID); err != nil { return err } default: @@ -87,7 +88,7 @@ func (e *Executor) execMove(s *ast.MoveStmt) error { // For cross-module moves, update all BY_NAME references throughout the project if isCrossModuleMove { - if err := e.updateQualifiedNameRefs(s.Name, targetModule.Name); err != nil { + if err := updateQualifiedNameRefs(ctx, s.Name, targetModule.Name); err != nil { return err } } @@ -96,7 +97,8 @@ func (e *Executor) execMove(s *ast.MoveStmt) error { } // updateQualifiedNameRefs updates all BY_NAME references to an element after a cross-module move. -func (e *Executor) updateQualifiedNameRefs(name ast.QualifiedName, newModule string) error { +func updateQualifiedNameRefs(ctx *ExecContext, name ast.QualifiedName, newModule string) error { + e := ctx.executor oldQN := name.String() // "OldModule.ElementName" newQN := newModule + "." + name.Name // "NewModule.ElementName" updated, err := e.writer.UpdateQualifiedNameInAllUnits(oldQN, newQN) @@ -104,13 +106,14 @@ func (e *Executor) updateQualifiedNameRefs(name ast.QualifiedName, newModule str return mdlerrors.NewBackend("update references", err) } if updated > 0 { - fmt.Fprintf(e.output, "Updated references in %d document(s): %s → %s\n", updated, oldQN, newQN) + fmt.Fprintf(ctx.Output, "Updated references in %d document(s): %s → %s\n", updated, oldQN, newQN) } return nil } // movePage moves a page to a new container. -func (e *Executor) movePage(name ast.QualifiedName, targetContainerID model.ID) error { +func movePage(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor // Find the page pages, err := e.reader.ListPages() if err != nil { @@ -131,7 +134,7 @@ func (e *Executor) movePage(name ast.QualifiedName, targetContainerID model.ID) if err := e.writer.MovePage(p); err != nil { return mdlerrors.NewBackend("move page", err) } - fmt.Fprintf(e.output, "Moved page %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved page %s to new location\n", name.String()) return nil } } @@ -140,7 +143,8 @@ func (e *Executor) movePage(name ast.QualifiedName, targetContainerID model.ID) } // moveMicroflow moves a microflow to a new container. -func (e *Executor) moveMicroflow(name ast.QualifiedName, targetContainerID model.ID) error { +func moveMicroflow(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor // Find the microflow mfs, err := e.reader.ListMicroflows() if err != nil { @@ -161,7 +165,7 @@ func (e *Executor) moveMicroflow(name ast.QualifiedName, targetContainerID model if err := e.writer.MoveMicroflow(mf); err != nil { return mdlerrors.NewBackend("move microflow", err) } - fmt.Fprintf(e.output, "Moved microflow %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved microflow %s to new location\n", name.String()) return nil } } @@ -170,7 +174,8 @@ func (e *Executor) moveMicroflow(name ast.QualifiedName, targetContainerID model } // moveSnippet moves a snippet to a new container. -func (e *Executor) moveSnippet(name ast.QualifiedName, targetContainerID model.ID) error { +func moveSnippet(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor // Find the snippet snippets, err := e.reader.ListSnippets() if err != nil { @@ -191,7 +196,7 @@ func (e *Executor) moveSnippet(name ast.QualifiedName, targetContainerID model.I if err := e.writer.MoveSnippet(s); err != nil { return mdlerrors.NewBackend("move snippet", err) } - fmt.Fprintf(e.output, "Moved snippet %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved snippet %s to new location\n", name.String()) return nil } } @@ -200,7 +205,8 @@ func (e *Executor) moveSnippet(name ast.QualifiedName, targetContainerID model.I } // moveNanoflow moves a nanoflow to a new container. -func (e *Executor) moveNanoflow(name ast.QualifiedName, targetContainerID model.ID) error { +func moveNanoflow(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor // Find the nanoflow nfs, err := e.reader.ListNanoflows() if err != nil { @@ -221,7 +227,7 @@ func (e *Executor) moveNanoflow(name ast.QualifiedName, targetContainerID model. if err := e.writer.MoveNanoflow(nf); err != nil { return mdlerrors.NewBackend("move nanoflow", err) } - fmt.Fprintf(e.output, "Moved nanoflow %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved nanoflow %s to new location\n", name.String()) return nil } } @@ -233,7 +239,8 @@ func (e *Executor) moveNanoflow(name ast.QualifiedName, targetContainerID model. // Entities are embedded inside DomainModel documents, so we must remove from source DM and add to target DM. // Associations referencing the entity are converted to CrossAssociations. // ViewEntitySourceDocuments for view entities are also moved. -func (e *Executor) moveEntity(name ast.QualifiedName, sourceModule, targetModule *model.Module) error { +func moveEntity(ctx *ExecContext, name ast.QualifiedName, sourceModule, targetModule *model.Module) error { + e := ctx.executor // Get source domain model sourceDM, err := e.reader.GetDomainModel(sourceModule.ID) if err != nil { @@ -270,7 +277,7 @@ func (e *Executor) moveEntity(name ast.QualifiedName, sourceModule, targetModule // Extract the original doc name (before the module prefix was changed). docName := name.Name // ViewEntitySourceDocument name matches the entity name if err := e.writer.MoveViewEntitySourceDocument(sourceModule.Name, targetModule.ID, docName); err != nil { - fmt.Fprintf(e.output, "Warning: Could not move ViewEntitySourceDocument: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: Could not move ViewEntitySourceDocument: %v\n", err) } } @@ -278,16 +285,16 @@ func (e *Executor) moveEntity(name ast.QualifiedName, sourceModule, targetModule oldQualifiedName := name.String() // e.g., "DmTest.Customer" newQualifiedName := targetModule.Name + "." + name.Name // e.g., "DmTest2.Customer" if oqlUpdated, err := e.writer.UpdateOqlQueriesForMovedEntity(oldQualifiedName, newQualifiedName); err != nil { - fmt.Fprintf(e.output, "Warning: Could not update OQL queries: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: Could not update OQL queries: %v\n", err) } else if oqlUpdated > 0 { - fmt.Fprintf(e.output, "Updated %d OQL query(ies) referencing %s\n", oqlUpdated, oldQualifiedName) + fmt.Fprintf(ctx.Output, "Updated %d OQL query(ies) referencing %s\n", oqlUpdated, oldQualifiedName) } - fmt.Fprintf(e.output, "Moved entity %s to %s\n", name.String(), targetModule.Name) + fmt.Fprintf(ctx.Output, "Moved entity %s to %s\n", name.String(), targetModule.Name) if len(convertedAssocs) > 0 { - fmt.Fprintf(e.output, "Converted %d association(s) to cross-module associations:\n", len(convertedAssocs)) + fmt.Fprintf(ctx.Output, "Converted %d association(s) to cross-module associations:\n", len(convertedAssocs)) for _, assocName := range convertedAssocs { - fmt.Fprintf(e.output, " - %s\n", assocName) + fmt.Fprintf(ctx.Output, " - %s\n", assocName) } } return nil @@ -295,7 +302,8 @@ func (e *Executor) moveEntity(name ast.QualifiedName, sourceModule, targetModule // moveEnumeration moves an enumeration to a new container. // For cross-module moves, updates all EnumerationAttributeType references across all domain models. -func (e *Executor) moveEnumeration(name ast.QualifiedName, targetContainerID model.ID, targetModuleName string) error { +func moveEnumeration(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID, targetModuleName string) error { + e := ctx.executor enum := e.findEnumeration(name.Module, name.Name) if enum == nil { return mdlerrors.NewNotFound("enumeration", name.String()) @@ -311,18 +319,19 @@ func (e *Executor) moveEnumeration(name ast.QualifiedName, targetContainerID mod if targetModuleName != "" && targetModuleName != name.Module { newQualifiedName := targetModuleName + "." + name.Name if err := e.writer.UpdateEnumerationRefsInAllDomainModels(oldQualifiedName, newQualifiedName); err != nil { - fmt.Fprintf(e.output, "Warning: Could not update enumeration references: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: Could not update enumeration references: %v\n", err) } else { - fmt.Fprintf(e.output, "Updated enumeration references: %s -> %s\n", oldQualifiedName, newQualifiedName) + fmt.Fprintf(ctx.Output, "Updated enumeration references: %s -> %s\n", oldQualifiedName, newQualifiedName) } } - fmt.Fprintf(e.output, "Moved enumeration %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved enumeration %s to new location\n", name.String()) return nil } // moveConstant moves a constant to a new container. -func (e *Executor) moveConstant(name ast.QualifiedName, targetContainerID model.ID) error { +func moveConstant(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor constants, err := e.reader.ListConstants() if err != nil { return mdlerrors.NewBackend("list constants", err) @@ -341,7 +350,7 @@ func (e *Executor) moveConstant(name ast.QualifiedName, targetContainerID model. if err := e.writer.MoveConstant(c); err != nil { return mdlerrors.NewBackend("move constant", err) } - fmt.Fprintf(e.output, "Moved constant %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved constant %s to new location\n", name.String()) return nil } } @@ -350,7 +359,8 @@ func (e *Executor) moveConstant(name ast.QualifiedName, targetContainerID model. } // moveDatabaseConnection moves a database connection to a new container. -func (e *Executor) moveDatabaseConnection(name ast.QualifiedName, targetContainerID model.ID) error { +func moveDatabaseConnection(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID) error { + e := ctx.executor connections, err := e.reader.ListDatabaseConnections() if err != nil { return mdlerrors.NewBackend("list database connections", err) @@ -369,7 +379,7 @@ func (e *Executor) moveDatabaseConnection(name ast.QualifiedName, targetContaine if err := e.writer.MoveDatabaseConnection(conn); err != nil { return mdlerrors.NewBackend("move database connection", err) } - fmt.Fprintf(e.output, "Moved database connection %s to new location\n", name.String()) + fmt.Fprintf(ctx.Output, "Moved database connection %s to new location\n", name.String()) return nil } } diff --git a/mdl/executor/cmd_oql_plan.go b/mdl/executor/cmd_oql_plan.go index 72958f23..b6f696d8 100644 --- a/mdl/executor/cmd_oql_plan.go +++ b/mdl/executor/cmd_oql_plan.go @@ -3,6 +3,7 @@ package executor import ( + "context" "encoding/json" "fmt" "regexp" @@ -60,14 +61,14 @@ type oqlPlanColumn struct { } // OqlQueryPlanELK generates a query plan visualization for a view entity's OQL query. -func (e *Executor) OqlQueryPlanELK(qualifiedName string, entity *domainmodel.Entity) error { +func OqlQueryPlanELK(ctx *ExecContext, qualifiedName string, entity *domainmodel.Entity) error { plan := parseOqlPlan(qualifiedName, entity.OqlQuery) out, err := json.MarshalIndent(plan, "", " ") if err != nil { return mdlerrors.NewBackend("marshal JSON", err) } - fmt.Fprint(e.output, string(out)) + fmt.Fprint(ctx.Output, string(out)) return nil } @@ -474,3 +475,9 @@ func buildScalarSubqueryTable(sel ast.OQLSelectItem, selectIndex, tableOffset in return t } + +// --- Executor method wrapper for backward compatibility --- + +func (e *Executor) OqlQueryPlanELK(qualifiedName string, entity *domainmodel.Entity) error { + return OqlQueryPlanELK(e.newExecContext(context.Background()), qualifiedName, entity) +} diff --git a/mdl/executor/cmd_rename.go b/mdl/executor/cmd_rename.go index 66b05062..8488c724 100644 --- a/mdl/executor/cmd_rename.go +++ b/mdl/executor/cmd_rename.go @@ -13,35 +13,37 @@ import ( ) // execRename handles RENAME statements for all document types. -func (e *Executor) execRename(s *ast.RenameStmt) error { +func execRename(ctx *ExecContext, s *ast.RenameStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } switch s.ObjectType { case "ENTITY": - return e.execRenameEntity(s) + return execRenameEntity(ctx, s) case "MICROFLOW": - return e.execRenameDocument(s, "microflow") + return execRenameDocument(ctx, s, "microflow") case "NANOFLOW": - return e.execRenameDocument(s, "nanoflow") + return execRenameDocument(ctx, s, "nanoflow") case "PAGE": - return e.execRenameDocument(s, "page") + return execRenameDocument(ctx, s, "page") case "ENUMERATION": - return e.execRenameEnumeration(s) + return execRenameEnumeration(ctx, s) case "ASSOCIATION": - return e.execRenameAssociation(s) + return execRenameAssociation(ctx, s) case "CONSTANT": - return e.execRenameDocument(s, "constant") + return execRenameDocument(ctx, s, "constant") case "MODULE": - return e.execRenameModule(s) + return execRenameModule(ctx, s) default: return mdlerrors.NewUnsupported(fmt.Sprintf("RENAME not supported for %s", s.ObjectType)) } } // execRenameEntity renames an entity and updates all BY_NAME references. -func (e *Executor) execRenameEntity(s *ast.RenameStmt) error { +func execRenameEntity(ctx *ExecContext, s *ast.RenameStmt) error { + e := ctx.executor // Find the entity module, err := e.findModule(s.Name.Module) if err != nil { @@ -74,7 +76,7 @@ func (e *Executor) execRenameEntity(s *ast.RenameStmt) error { } if s.DryRun { - e.printRenameReport(oldQualifiedName, newQualifiedName, hits) + printRenameReport(ctx, oldQualifiedName, newQualifiedName, hits) return nil } @@ -92,15 +94,16 @@ func (e *Executor) execRenameEntity(s *ast.RenameStmt) error { e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed entity: %s → %s\n", oldQualifiedName, newQualifiedName) + fmt.Fprintf(ctx.Output, "Renamed entity: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { - fmt.Fprintf(e.output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) + fmt.Fprintf(ctx.Output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) } return nil } // execRenameModule renames a module and updates all BY_NAME references with the module prefix. -func (e *Executor) execRenameModule(s *ast.RenameStmt) error { +func execRenameModule(ctx *ExecContext, s *ast.RenameStmt) error { + e := ctx.executor oldModuleName := s.Name.Module newModuleName := s.NewName @@ -126,7 +129,7 @@ func (e *Executor) execRenameModule(s *ast.RenameStmt) error { allHits := mergeHits(hits, exactHits) if s.DryRun { - e.printRenameReport(oldModuleName, newModuleName, allHits) + printRenameReport(ctx, oldModuleName, newModuleName, allHits) return nil } @@ -139,9 +142,9 @@ func (e *Executor) execRenameModule(s *ast.RenameStmt) error { e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed module: %s → %s\n", oldModuleName, newModuleName) + fmt.Fprintf(ctx.Output, "Renamed module: %s → %s\n", oldModuleName, newModuleName) if len(allHits) > 0 { - fmt.Fprintf(e.output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(allHits), len(allHits)) + fmt.Fprintf(ctx.Output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(allHits), len(allHits)) } return nil } @@ -150,7 +153,8 @@ func (e *Executor) execRenameModule(s *ast.RenameStmt) error { // These are standalone documents where the Name field is in the document BSON itself. // The reference scanner handles updating all BY_NAME references, and then we update // the document's own Name field via a raw BSON rewrite. -func (e *Executor) execRenameDocument(s *ast.RenameStmt, docType string) error { +func execRenameDocument(ctx *ExecContext, s *ast.RenameStmt, docType string) error { + e := ctx.executor oldQualifiedName := s.Name.Module + "." + s.Name.Name newQualifiedName := s.Name.Module + "." + s.NewName @@ -215,7 +219,7 @@ func (e *Executor) execRenameDocument(s *ast.RenameStmt, docType string) error { } if s.DryRun { - e.printRenameReport(oldQualifiedName, newQualifiedName, hits) + printRenameReport(ctx, oldQualifiedName, newQualifiedName, hits) return nil } @@ -226,15 +230,16 @@ func (e *Executor) execRenameDocument(s *ast.RenameStmt, docType string) error { e.invalidateHierarchy() - fmt.Fprintf(e.output, "Renamed %s: %s → %s\n", docType, oldQualifiedName, newQualifiedName) + fmt.Fprintf(ctx.Output, "Renamed %s: %s → %s\n", docType, oldQualifiedName, newQualifiedName) if len(hits) > 0 { - fmt.Fprintf(e.output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) + fmt.Fprintf(ctx.Output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) } return nil } // execRenameEnumeration renames an enumeration and updates all references. -func (e *Executor) execRenameEnumeration(s *ast.RenameStmt) error { +func execRenameEnumeration(ctx *ExecContext, s *ast.RenameStmt) error { + e := ctx.executor oldQualifiedName := s.Name.Module + "." + s.Name.Name newQualifiedName := s.Name.Module + "." + s.NewName @@ -265,7 +270,7 @@ func (e *Executor) execRenameEnumeration(s *ast.RenameStmt) error { } if s.DryRun { - e.printRenameReport(oldQualifiedName, newQualifiedName, hits) + printRenameReport(ctx, oldQualifiedName, newQualifiedName, hits) return nil } @@ -280,15 +285,16 @@ func (e *Executor) execRenameEnumeration(s *ast.RenameStmt) error { e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed enumeration: %s → %s\n", oldQualifiedName, newQualifiedName) + fmt.Fprintf(ctx.Output, "Renamed enumeration: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { - fmt.Fprintf(e.output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) + fmt.Fprintf(ctx.Output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) } return nil } // execRenameAssociation renames an association and updates all references. -func (e *Executor) execRenameAssociation(s *ast.RenameStmt) error { +func execRenameAssociation(ctx *ExecContext, s *ast.RenameStmt) error { + e := ctx.executor oldQualifiedName := s.Name.Module + "." + s.Name.Name newQualifiedName := s.Name.Module + "." + s.NewName @@ -319,7 +325,7 @@ func (e *Executor) execRenameAssociation(s *ast.RenameStmt) error { } if s.DryRun { - e.printRenameReport(oldQualifiedName, newQualifiedName, hits) + printRenameReport(ctx, oldQualifiedName, newQualifiedName, hits) return nil } @@ -337,17 +343,17 @@ func (e *Executor) execRenameAssociation(s *ast.RenameStmt) error { e.invalidateHierarchy() e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed association: %s → %s\n", oldQualifiedName, newQualifiedName) + fmt.Fprintf(ctx.Output, "Renamed association: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { - fmt.Fprintf(e.output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) + fmt.Fprintf(ctx.Output, "Updated %d reference(s) in %d document(s)\n", totalRefCount(hits), len(hits)) } return nil } // printRenameReport outputs a dry-run report of what would change. -func (e *Executor) printRenameReport(oldName, newName string, hits []mpr.RenameHit) { - fmt.Fprintf(e.output, "Would rename: %s → %s\n", oldName, newName) - fmt.Fprintf(e.output, "References found: %d in %d document(s)\n", totalRefCount(hits), len(hits)) +func printRenameReport(ctx *ExecContext, oldName, newName string, hits []mpr.RenameHit) { + fmt.Fprintf(ctx.Output, "Would rename: %s → %s\n", oldName, newName) + fmt.Fprintf(ctx.Output, "References found: %d in %d document(s)\n", totalRefCount(hits), len(hits)) for _, h := range hits { label := h.Name @@ -358,7 +364,7 @@ func (e *Executor) printRenameReport(oldName, newName string, hits []mpr.RenameH if idx := strings.Index(typeName, "$"); idx >= 0 { typeName = typeName[idx+1:] } - fmt.Fprintf(e.output, " %s (%s) — %d reference(s)\n", label, typeName, h.Count) + fmt.Fprintf(ctx.Output, " %s (%s) — %d reference(s)\n", label, typeName, h.Count) } } diff --git a/mdl/executor/cmd_search.go b/mdl/executor/cmd_search.go index ccef9093..fa6aef20 100644 --- a/mdl/executor/cmd_search.go +++ b/mdl/executor/cmd_search.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "strings" @@ -11,7 +12,8 @@ import ( ) // execShowCallers handles SHOW CALLERS OF Module.Microflow [TRANSITIVE]. -func (e *Executor) execShowCallers(s *ast.ShowStmt) error { +func execShowCallers(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW CALLERS") } @@ -22,11 +24,11 @@ func (e *Executor) execShowCallers(s *ast.ShowStmt) error { } targetName := s.Name.String() - fmt.Fprintf(e.output, "\nCallers of %s", targetName) + fmt.Fprintf(ctx.Output, "\nCallers of %s", targetName) if s.Transitive { - fmt.Fprintln(e.output, " (transitive)") + fmt.Fprintln(ctx.Output, " (transitive)") } else { - fmt.Fprintln(e.output, "") + fmt.Fprintln(ctx.Output, "") } var query string @@ -58,23 +60,24 @@ func (e *Executor) execShowCallers(s *ast.ShowStmt) error { ` } - result, err := e.catalog.Query(strings.Replace(query, "?", "'"+targetName+"'", 1)) + result, err := ctx.Catalog.Query(strings.Replace(query, "?", "'"+targetName+"'", 1)) if err != nil { return mdlerrors.NewBackend("query callers", err) } if result.Count == 0 { - fmt.Fprintln(e.output, "(no callers found)") + fmt.Fprintln(ctx.Output, "(no callers found)") return nil } - fmt.Fprintf(e.output, "Found %d caller(s)\n", result.Count) + fmt.Fprintf(ctx.Output, "Found %d caller(s)\n", result.Count) e.outputCatalogResults(result) return nil } // execShowCallees handles SHOW CALLEES OF Module.Microflow [TRANSITIVE]. -func (e *Executor) execShowCallees(s *ast.ShowStmt) error { +func execShowCallees(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW CALLEES") } @@ -85,11 +88,11 @@ func (e *Executor) execShowCallees(s *ast.ShowStmt) error { } sourceName := s.Name.String() - fmt.Fprintf(e.output, "\nCallees of %s", sourceName) + fmt.Fprintf(ctx.Output, "\nCallees of %s", sourceName) if s.Transitive { - fmt.Fprintln(e.output, " (transitive)") + fmt.Fprintln(ctx.Output, " (transitive)") } else { - fmt.Fprintln(e.output, "") + fmt.Fprintln(ctx.Output, "") } var query string @@ -121,23 +124,24 @@ func (e *Executor) execShowCallees(s *ast.ShowStmt) error { ` } - result, err := e.catalog.Query(strings.Replace(query, "?", "'"+sourceName+"'", 1)) + result, err := ctx.Catalog.Query(strings.Replace(query, "?", "'"+sourceName+"'", 1)) if err != nil { return mdlerrors.NewBackend("query callees", err) } if result.Count == 0 { - fmt.Fprintln(e.output, "(no callees found)") + fmt.Fprintln(ctx.Output, "(no callees found)") return nil } - fmt.Fprintf(e.output, "Found %d callee(s)\n", result.Count) + fmt.Fprintf(ctx.Output, "Found %d callee(s)\n", result.Count) e.outputCatalogResults(result) return nil } // execShowReferences handles SHOW REFERENCES TO Module.Entity. -func (e *Executor) execShowReferences(s *ast.ShowStmt) error { +func execShowReferences(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW REFERENCES") } @@ -148,7 +152,7 @@ func (e *Executor) execShowReferences(s *ast.ShowStmt) error { } targetName := s.Name.String() - fmt.Fprintf(e.output, "\nReferences to %s\n", targetName) + fmt.Fprintf(ctx.Output, "\nReferences to %s\n", targetName) // Find all references to this target query := ` @@ -158,24 +162,25 @@ func (e *Executor) execShowReferences(s *ast.ShowStmt) error { ORDER BY RefKind, SourceType, SourceName ` - result, err := e.catalog.Query(strings.Replace(query, "?", "'"+targetName+"'", 1)) + result, err := ctx.Catalog.Query(strings.Replace(query, "?", "'"+targetName+"'", 1)) if err != nil { return mdlerrors.NewBackend("query references", err) } if result.Count == 0 { - fmt.Fprintln(e.output, "(no references found)") + fmt.Fprintln(ctx.Output, "(no references found)") return nil } - fmt.Fprintf(e.output, "Found %d reference(s)\n", result.Count) + fmt.Fprintf(ctx.Output, "Found %d reference(s)\n", result.Count) e.outputCatalogResults(result) return nil } // execShowImpact handles SHOW IMPACT OF Module.Entity. // This shows all elements that would be affected by changing the target. -func (e *Executor) execShowImpact(s *ast.ShowStmt) error { +func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW IMPACT") } @@ -186,7 +191,7 @@ func (e *Executor) execShowImpact(s *ast.ShowStmt) error { } targetName := s.Name.String() - fmt.Fprintf(e.output, "\nImpact analysis for %s\n", targetName) + fmt.Fprintf(ctx.Output, "\nImpact analysis for %s\n", targetName) // Find all direct references to this target directQuery := ` @@ -196,13 +201,13 @@ func (e *Executor) execShowImpact(s *ast.ShowStmt) error { ORDER BY SourceType, SourceName ` - result, err := e.catalog.Query(strings.Replace(directQuery, "?", "'"+targetName+"'", 1)) + result, err := ctx.Catalog.Query(strings.Replace(directQuery, "?", "'"+targetName+"'", 1)) if err != nil { return mdlerrors.NewBackend("query impact", err) } if result.Count == 0 { - fmt.Fprintln(e.output, "(no impact - element is not referenced)") + fmt.Fprintln(ctx.Output, "(no impact - element is not referenced)") return nil } @@ -216,14 +221,32 @@ func (e *Executor) execShowImpact(s *ast.ShowStmt) error { } } - fmt.Fprintf(e.output, "\nSummary:\n") + fmt.Fprintf(ctx.Output, "\nSummary:\n") for t, count := range typeCounts { - fmt.Fprintf(e.output, " %s: %d\n", t, count) + fmt.Fprintf(ctx.Output, " %s: %d\n", t, count) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) - fmt.Fprintf(e.output, "Found %d affected element(s)\n", result.Count) + fmt.Fprintf(ctx.Output, "Found %d affected element(s)\n", result.Count) e.outputCatalogResults(result) return nil } + +// --- Executor method wrappers for backward compatibility --- + +func (e *Executor) execShowCallers(s *ast.ShowStmt) error { + return execShowCallers(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execShowCallees(s *ast.ShowStmt) error { + return execShowCallees(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execShowReferences(s *ast.ShowStmt) error { + return execShowReferences(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execShowImpact(s *ast.ShowStmt) error { + return execShowImpact(e.newExecContext(context.Background()), s) +} diff --git a/mdl/executor/cmd_sql.go b/mdl/executor/cmd_sql.go index 4e0b7de3..390bb62b 100644 --- a/mdl/executor/cmd_sql.go +++ b/mdl/executor/cmd_sql.go @@ -15,7 +15,8 @@ import ( ) // ensureSQLManager lazily initializes the SQL connection manager. -func (e *Executor) ensureSQLManager() *sqllib.Manager { +func ensureSQLManager(ctx *ExecContext) *sqllib.Manager { + e := ctx.executor if e.sqlMgr == nil { e.sqlMgr = sqllib.NewManager() } @@ -23,15 +24,15 @@ func (e *Executor) ensureSQLManager() *sqllib.Manager { } // getOrAutoConnect returns an existing connection or auto-connects using connections.yaml. -func (e *Executor) getOrAutoConnect(alias string) (*sqllib.Connection, error) { - mgr := e.ensureSQLManager() +func getOrAutoConnect(ctx *ExecContext, alias string) (*sqllib.Connection, error) { + mgr := ensureSQLManager(ctx) conn, err := mgr.Get(alias) if err == nil { return conn, nil } // Not connected yet — try auto-connect from config - if acErr := e.autoConnect(alias); acErr != nil { + if acErr := autoConnect(ctx, alias); acErr != nil { return nil, mdlerrors.NewNotFoundMsg("connection", alias, fmt.Sprintf("no connection '%s' (and auto-connect failed: %v)", alias, acErr)) } return mgr.Get(alias) @@ -39,10 +40,10 @@ func (e *Executor) getOrAutoConnect(alias string) (*sqllib.Connection, error) { // execSQLConnect handles SQL CONNECT '' AS // and SQL CONNECT (resolve from connections.yaml). -func (e *Executor) execSQLConnect(s *ast.SQLConnectStmt) error { +func execSQLConnect(ctx *ExecContext, s *ast.SQLConnectStmt) error { if s.DSN == "" && s.Driver == "" { // Short form: SQL CONNECT — resolve from config - return e.autoConnect(s.Alias) + return autoConnect(ctx, s.Alias) } driver, err := sqllib.ParseDriver(s.Driver) @@ -50,50 +51,50 @@ func (e *Executor) execSQLConnect(s *ast.SQLConnectStmt) error { return err } - mgr := e.ensureSQLManager() + mgr := ensureSQLManager(ctx) if err := mgr.Connect(driver, s.DSN, s.Alias); err != nil { return err } - fmt.Fprintf(e.output, "Connected to %s database as '%s'\n", driver, s.Alias) + fmt.Fprintf(ctx.Output, "Connected to %s database as '%s'\n", driver, s.Alias) return nil } // autoConnect resolves a connection alias from env vars or .mxcli/connections.yaml // and connects automatically. -func (e *Executor) autoConnect(alias string) error { +func autoConnect(ctx *ExecContext, alias string) error { rc, err := sqllib.ResolveConnection(sqllib.ResolveOptions{Alias: alias}) if err != nil { return fmt.Errorf("cannot resolve connection '%s': %w\nAdd it to .mxcli/connections.yaml or use: SQL CONNECT '' AS %s", alias, err, alias) } - mgr := e.ensureSQLManager() + mgr := ensureSQLManager(ctx) if err := mgr.Connect(rc.Driver, rc.DSN, alias); err != nil { return err } - fmt.Fprintf(e.output, "Connected to %s database as '%s' (from config)\n", rc.Driver, alias) + fmt.Fprintf(ctx.Output, "Connected to %s database as '%s' (from config)\n", rc.Driver, alias) return nil } // execSQLDisconnect handles SQL DISCONNECT -func (e *Executor) execSQLDisconnect(s *ast.SQLDisconnectStmt) error { - mgr := e.ensureSQLManager() +func execSQLDisconnect(ctx *ExecContext, s *ast.SQLDisconnectStmt) error { + mgr := ensureSQLManager(ctx) if err := mgr.Disconnect(s.Alias); err != nil { return err } - fmt.Fprintf(e.output, "Disconnected '%s'\n", s.Alias) + fmt.Fprintf(ctx.Output, "Disconnected '%s'\n", s.Alias) return nil } // execSQLConnections handles SQL CONNECTIONS -func (e *Executor) execSQLConnections() error { - mgr := e.ensureSQLManager() +func execSQLConnections(ctx *ExecContext) error { + mgr := ensureSQLManager(ctx) infos := mgr.List() if len(infos) == 0 { - fmt.Fprintln(e.output, "No active SQL connections") + fmt.Fprintln(ctx.Output, "No active SQL connections") return nil } @@ -108,98 +109,98 @@ func (e *Executor) execSQLConnections() error { for _, info := range infos { result.Rows = append(result.Rows, []any{info.Alias, string(info.Driver)}) } - sqllib.FormatTable(e.output, result) + sqllib.FormatTable(ctx.Output, result) return nil } // execSQLQuery handles SQL -func (e *Executor) execSQLQuery(s *ast.SQLQueryStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLQuery(ctx *ExecContext, s *ast.SQLQueryStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - result, err := sqllib.Execute(ctx, conn, s.Query) + result, err := sqllib.Execute(goCtx, conn, s.Query) if err != nil { return err } - sqllib.FormatTable(e.output, result) - fmt.Fprintf(e.output, "(%d rows)\n", len(result.Rows)) + sqllib.FormatTable(ctx.Output, result) + fmt.Fprintf(ctx.Output, "(%d rows)\n", len(result.Rows)) return nil } // execSQLShowTables handles SQL SHOW TABLES -func (e *Executor) execSQLShowTables(s *ast.SQLShowTablesStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLShowTables(ctx *ExecContext, s *ast.SQLShowTablesStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - result, err := sqllib.ShowTables(ctx, conn) + result, err := sqllib.ShowTables(goCtx, conn) if err != nil { return err } - sqllib.FormatTable(e.output, result) - fmt.Fprintf(e.output, "(%d tables)\n", len(result.Rows)) + sqllib.FormatTable(ctx.Output, result) + fmt.Fprintf(ctx.Output, "(%d tables)\n", len(result.Rows)) return nil } // execSQLShowViews handles SQL SHOW VIEWS -func (e *Executor) execSQLShowViews(s *ast.SQLShowViewsStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLShowViews(ctx *ExecContext, s *ast.SQLShowViewsStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - result, err := sqllib.ShowViews(ctx, conn) + result, err := sqllib.ShowViews(goCtx, conn) if err != nil { return err } - sqllib.FormatTable(e.output, result) - fmt.Fprintf(e.output, "(%d views)\n", len(result.Rows)) + sqllib.FormatTable(ctx.Output, result) + fmt.Fprintf(ctx.Output, "(%d views)\n", len(result.Rows)) return nil } // execSQLShowFunctions handles SQL SHOW FUNCTIONS -func (e *Executor) execSQLShowFunctions(s *ast.SQLShowFunctionsStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLShowFunctions(ctx *ExecContext, s *ast.SQLShowFunctionsStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - result, err := sqllib.ShowFunctions(ctx, conn) + result, err := sqllib.ShowFunctions(goCtx, conn) if err != nil { return err } - sqllib.FormatTable(e.output, result) - fmt.Fprintf(e.output, "(%d functions)\n", len(result.Rows)) + sqllib.FormatTable(ctx.Output, result) + fmt.Fprintf(ctx.Output, "(%d functions)\n", len(result.Rows)) return nil } // execSQLGenerateConnector handles SQL GENERATE CONNECTOR INTO [TABLES (...)] [VIEWS (...)] [EXEC] -func (e *Executor) execSQLGenerateConnector(s *ast.SQLGenerateConnectorStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLGenerateConnector(ctx *ExecContext, s *ast.SQLGenerateConnectorStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() cfg := &sqllib.GenerateConfig{ @@ -210,37 +211,38 @@ func (e *Executor) execSQLGenerateConnector(s *ast.SQLGenerateConnectorStmt) err Views: s.Views, } - result, err := sqllib.GenerateConnector(ctx, cfg) + result, err := sqllib.GenerateConnector(goCtx, cfg) if err != nil { return err } // Report skipped columns for _, skip := range result.SkippedCols { - fmt.Fprintf(e.output, "-- WARNING: skipped unmappable column: %s\n", skip) + fmt.Fprintf(ctx.Output, "-- WARNING: skipped unmappable column: %s\n", skip) } if s.Exec { // Execute constants + entities (parseable by mxcli) - fmt.Fprintf(e.output, "Generating connector (%d tables, %d views)...\n", + fmt.Fprintf(ctx.Output, "Generating connector (%d tables, %d views)...\n", result.TableCount, result.ViewCount) - if err := e.executeGeneratedMDL(result.ExecutableMDL); err != nil { + if err := executeGeneratedMDL(ctx, result.ExecutableMDL); err != nil { return err } // Print DATABASE CONNECTION as reference (not yet executable) - fmt.Fprintf(e.output, "\n-- Database Connection definition (configure in Studio Pro with Database Connector module):\n") - fmt.Fprint(e.output, result.ConnectionMDL) + fmt.Fprintf(ctx.Output, "\n-- Database Connection definition (configure in Studio Pro with Database Connector module):\n") + fmt.Fprint(ctx.Output, result.ConnectionMDL) return nil } // Print complete MDL to output - fmt.Fprint(e.output, result.MDL) - fmt.Fprintf(e.output, "\n-- Generated: %d tables, %d views\n", result.TableCount, result.ViewCount) + fmt.Fprint(ctx.Output, result.MDL) + fmt.Fprintf(ctx.Output, "\n-- Generated: %d tables, %d views\n", result.TableCount, result.ViewCount) return nil } // executeGeneratedMDL parses and executes MDL text as if it were a script. -func (e *Executor) executeGeneratedMDL(mdl string) error { +func executeGeneratedMDL(ctx *ExecContext, mdl string) error { + e := ctx.executor prog, errs := visitor.Build(mdl) if len(errs) > 0 { return mdlerrors.NewBackend("parse generated MDL", fmt.Errorf("%v", errs[0])) @@ -249,21 +251,59 @@ func (e *Executor) executeGeneratedMDL(mdl string) error { } // execSQLDescribeTable handles SQL DESCRIBE -func (e *Executor) execSQLDescribeTable(s *ast.SQLDescribeTableStmt) error { - conn, err := e.getOrAutoConnect(s.Alias) +func execSQLDescribeTable(ctx *ExecContext, s *ast.SQLDescribeTableStmt) error { + conn, err := getOrAutoConnect(ctx, s.Alias) if err != nil { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + goCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - result, err := sqllib.DescribeTable(ctx, conn, s.Table) + result, err := sqllib.DescribeTable(goCtx, conn, s.Table) if err != nil { return err } - sqllib.FormatTable(e.output, result) - fmt.Fprintf(e.output, "(%d columns)\n", len(result.Rows)) + sqllib.FormatTable(ctx.Output, result) + fmt.Fprintf(ctx.Output, "(%d columns)\n", len(result.Rows)) return nil } + +// Executor wrappers for unmigrated callers. + +func (e *Executor) execSQLConnect(s *ast.SQLConnectStmt) error { + return execSQLConnect(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLDisconnect(s *ast.SQLDisconnectStmt) error { + return execSQLDisconnect(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLConnections() error { + return execSQLConnections(e.newExecContext(context.Background())) +} + +func (e *Executor) execSQLQuery(s *ast.SQLQueryStmt) error { + return execSQLQuery(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLShowTables(s *ast.SQLShowTablesStmt) error { + return execSQLShowTables(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLShowViews(s *ast.SQLShowViewsStmt) error { + return execSQLShowViews(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLShowFunctions(s *ast.SQLShowFunctionsStmt) error { + return execSQLShowFunctions(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLGenerateConnector(s *ast.SQLGenerateConnectorStmt) error { + return execSQLGenerateConnector(e.newExecContext(context.Background()), s) +} + +func (e *Executor) execSQLDescribeTable(s *ast.SQLDescribeTableStmt) error { + return execSQLDescribeTable(e.newExecContext(context.Background()), s) +} diff --git a/mdl/executor/cmd_structure.go b/mdl/executor/cmd_structure.go index eebfb157..7888fe9e 100644 --- a/mdl/executor/cmd_structure.go +++ b/mdl/executor/cmd_structure.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "sort" "strings" @@ -17,7 +18,8 @@ import ( ) // execShowStructure handles SHOW STRUCTURE [DEPTH n] [IN module] [ALL]. -func (e *Executor) execShowStructure(s *ast.ShowStmt) error { +func execShowStructure(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -30,53 +32,59 @@ func (e *Executor) execShowStructure(s *ast.ShowStmt) error { } // Get modules from catalog - modules, err := e.getStructureModules(s.InModule, s.All) + modules, err := getStructureModules(ctx, s.InModule, s.All) if err != nil { return err } if len(modules) == 0 { - if e.format == FormatJSON { - fmt.Fprintln(e.output, "[]") + if ctx.Format == FormatJSON { + fmt.Fprintln(ctx.Output, "[]") } else { - fmt.Fprintln(e.output, "(no modules found)") + fmt.Fprintln(ctx.Output, "(no modules found)") } return nil } // JSON mode: emit structured table - if e.format == FormatJSON { - return e.structureDepth1JSON(modules) + if ctx.Format == FormatJSON { + return structureDepth1JSON(ctx, modules) } switch depth { case 1: - return e.structureDepth1(modules) + return structureDepth1(ctx, modules) case 2: - return e.structureDepth2(modules) + return structureDepth2(ctx, modules) case 3: - return e.structureDepth3(modules) + return structureDepth3(ctx, modules) default: - return e.structureDepth2(modules) + return structureDepth2(ctx, modules) } } +// Executor method wrapper for unmigrated callers. +func (e *Executor) execShowStructure(s *ast.ShowStmt) error { + return execShowStructure(e.newExecContext(context.Background()), s) +} + // structureDepth1JSON emits structure as a JSON table with one row per module // and columns for each element type count. -func (e *Executor) structureDepth1JSON(modules []structureModule) error { - entityCounts := e.queryCountByModule("entities") - mfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'MICROFLOW'") - nfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'NANOFLOW'") - pageCounts := e.queryCountByModule("pages") - enumCounts := e.queryCountByModule("enumerations") - snippetCounts := e.queryCountByModule("snippets") - jaCounts := e.queryCountByModule("java_actions") - wfCounts := e.queryCountByModule("workflows") - odataClientCounts := e.queryCountByModule("odata_clients") - odataServiceCounts := e.queryCountByModule("odata_services") - beServiceCounts := e.queryCountByModule("business_event_services") - constantCounts := e.countByModuleFromReader("constants") - scheduledEventCounts := e.countByModuleFromReader("scheduled_events") +func structureDepth1JSON(ctx *ExecContext, modules []structureModule) error { + e := ctx.executor + entityCounts := queryCountByModule(ctx, "entities") + mfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'MICROFLOW'") + nfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'NANOFLOW'") + pageCounts := queryCountByModule(ctx, "pages") + enumCounts := queryCountByModule(ctx, "enumerations") + snippetCounts := queryCountByModule(ctx, "snippets") + jaCounts := queryCountByModule(ctx, "java_actions") + wfCounts := queryCountByModule(ctx, "workflows") + odataClientCounts := queryCountByModule(ctx, "odata_clients") + odataServiceCounts := queryCountByModule(ctx, "odata_services") + beServiceCounts := queryCountByModule(ctx, "business_event_services") + constantCounts := countByModuleFromReader(ctx, "constants") + scheduledEventCounts := countByModuleFromReader(ctx, "scheduled_events") tr := &TableResult{ Columns: []string{ @@ -113,8 +121,8 @@ type structureModule struct { } // getStructureModules returns filtered and sorted modules for structure output. -func (e *Executor) getStructureModules(filterModule string, includeAll bool) ([]structureModule, error) { - result, err := e.catalog.Query("SELECT Id, Name, Source, AppStoreGuid FROM modules ORDER BY Name") +func getStructureModules(ctx *ExecContext, filterModule string, includeAll bool) ([]structureModule, error) { + result, err := ctx.Catalog.Query("SELECT Id, Name, Source, AppStoreGuid FROM modules ORDER BY Name") if err != nil { return nil, mdlerrors.NewBackend("query modules", err) } @@ -181,23 +189,23 @@ func asString(v any) string { // Depth 1 — Module Summary // ============================================================================ -func (e *Executor) structureDepth1(modules []structureModule) error { +func structureDepth1(ctx *ExecContext, modules []structureModule) error { // Query counts per module from catalog - entityCounts := e.queryCountByModule("entities") - mfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'MICROFLOW'") - nfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'NANOFLOW'") - pageCounts := e.queryCountByModule("pages") - enumCounts := e.queryCountByModule("enumerations") - snippetCounts := e.queryCountByModule("snippets") - jaCounts := e.queryCountByModule("java_actions") - wfCounts := e.queryCountByModule("workflows") - odataClientCounts := e.queryCountByModule("odata_clients") - odataServiceCounts := e.queryCountByModule("odata_services") - beServiceCounts := e.queryCountByModule("business_event_services") + entityCounts := queryCountByModule(ctx, "entities") + mfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'MICROFLOW'") + nfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'NANOFLOW'") + pageCounts := queryCountByModule(ctx, "pages") + enumCounts := queryCountByModule(ctx, "enumerations") + snippetCounts := queryCountByModule(ctx, "snippets") + jaCounts := queryCountByModule(ctx, "java_actions") + wfCounts := queryCountByModule(ctx, "workflows") + odataClientCounts := queryCountByModule(ctx, "odata_clients") + odataServiceCounts := queryCountByModule(ctx, "odata_services") + beServiceCounts := queryCountByModule(ctx, "business_event_services") // Get constants and scheduled events from reader (no catalog tables) - constantCounts := e.countByModuleFromReader("constants") - scheduledEventCounts := e.countByModuleFromReader("scheduled_events") + constantCounts := countByModuleFromReader(ctx, "constants") + scheduledEventCounts := countByModuleFromReader(ctx, "scheduled_events") // Calculate name column width for alignment nameWidth := 0 @@ -251,17 +259,17 @@ func (e *Executor) structureDepth1(modules []structureModule) error { } if len(parts) > 0 { - fmt.Fprintf(e.output, "%-*s %s\n", nameWidth, m.Name, strings.Join(parts, ", ")) + fmt.Fprintf(ctx.Output, "%-*s %s\n", nameWidth, m.Name, strings.Join(parts, ", ")) } } return nil } // queryCountByModule queries a catalog table and returns a map of module name → count. -func (e *Executor) queryCountByModule(tableAndWhere string) map[string]int { +func queryCountByModule(ctx *ExecContext, tableAndWhere string) map[string]int { counts := make(map[string]int) sql := fmt.Sprintf("SELECT ModuleName, COUNT(*) FROM %s GROUP BY ModuleName", tableAndWhere) - result, err := e.catalog.Query(sql) + result, err := ctx.Catalog.Query(sql) if err != nil { return counts } @@ -273,7 +281,8 @@ func (e *Executor) queryCountByModule(tableAndWhere string) map[string]int { } // countByModuleFromReader counts elements per module using the reader (for types without catalog tables). -func (e *Executor) countByModuleFromReader(kind string) map[string]int { +func countByModuleFromReader(ctx *ExecContext, kind string) map[string]int { + e := ctx.executor counts := make(map[string]int) h, err := e.getHierarchy() if err != nil { @@ -313,7 +322,8 @@ func pluralize(count int, singular, plural string) string { // Depth 2 — Elements with Signatures // ============================================================================ -func (e *Executor) structureDepth2(modules []structureModule) error { +func structureDepth2(ctx *ExecContext, modules []structureModule) error { + e := ctx.executor // Pre-load data that needs the reader h, err := e.getHierarchy() if err != nil { @@ -394,12 +404,12 @@ func (e *Executor) structureDepth2(modules []structureModule) error { for i, m := range modules { if i > 0 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, m.Name) + fmt.Fprintln(ctx.Output, m.Name) // Entities - e.structureEntities(m.Name, dmByModule[m.Name], false) + structureEntities(ctx, m.Name, dmByModule[m.Name], false) // Enumerations if enums, ok := enumsByModule[m.Name]; ok { @@ -409,7 +419,7 @@ func (e *Executor) structureDepth2(modules []structureModule) error { for i, v := range enum.Values { values[i] = v.Name } - fmt.Fprintf(e.output, " Enumeration %s.%s [%s]\n", m.Name, enum.Name, strings.Join(values, ", ")) + fmt.Fprintf(ctx.Output, " Enumeration %s.%s [%s]\n", m.Name, enum.Name, strings.Join(values, ", ")) } } @@ -417,7 +427,7 @@ func (e *Executor) structureDepth2(modules []structureModule) error { if mfs, ok := mfByModule[m.Name]; ok { sortMicroflows(mfs) for _, mf := range mfs { - fmt.Fprintf(e.output, " Microflow %s.%s%s\n", m.Name, mf.Name, formatMicroflowSignature(mf.Parameters, mf.ReturnType, false)) + fmt.Fprintf(ctx.Output, " Microflow %s.%s%s\n", m.Name, mf.Name, formatMicroflowSignature(mf.Parameters, mf.ReturnType, false)) } } @@ -425,27 +435,27 @@ func (e *Executor) structureDepth2(modules []structureModule) error { if nfs, ok := nfByModule[m.Name]; ok { sortNanoflows(nfs) for _, nf := range nfs { - fmt.Fprintf(e.output, " Nanoflow %s.%s%s\n", m.Name, nf.Name, formatMicroflowSignature(nf.Parameters, nf.ReturnType, false)) + fmt.Fprintf(ctx.Output, " Nanoflow %s.%s%s\n", m.Name, nf.Name, formatMicroflowSignature(nf.Parameters, nf.ReturnType, false)) } } // Workflows - e.structureWorkflows(m.Name, wfByModule[m.Name], false) + structureWorkflows(ctx, m.Name, wfByModule[m.Name], false) // Pages (from catalog) - e.structurePages(m.Name) + structurePages(ctx, m.Name) // Snippets (from catalog) - e.structureSnippets(m.Name) + structureSnippets(ctx, m.Name) // Java Actions - e.outputJavaActions(m.Name, jaByModule[m.Name], false) + outputJavaActions(ctx, m.Name, jaByModule[m.Name], false) // Constants if consts, ok := constByModule[m.Name]; ok { sortConstants(consts) for _, c := range consts { - fmt.Fprintf(e.output, " Constant %s.%s: %s\n", m.Name, c.Name, formatConstantTypeBrief(c.Type)) + fmt.Fprintf(ctx.Output, " Constant %s.%s: %s\n", m.Name, c.Name, formatConstantTypeBrief(c.Type)) } } @@ -453,18 +463,18 @@ func (e *Executor) structureDepth2(modules []structureModule) error { if events, ok := eventsByModule[m.Name]; ok { sortScheduledEvents(events) for _, ev := range events { - fmt.Fprintf(e.output, " ScheduledEvent %s.%s\n", m.Name, ev.Name) + fmt.Fprintf(ctx.Output, " ScheduledEvent %s.%s\n", m.Name, ev.Name) } } // OData Clients - e.structureODataClients(m.Name) + structureODataClients(ctx, m.Name) // OData Services - e.structureODataServices(m.Name) + structureODataServices(ctx, m.Name) // Business Event Services - e.structureBusinessEventServices(m.Name) + structureBusinessEventServices(ctx, m.Name) } return nil @@ -474,7 +484,8 @@ func (e *Executor) structureDepth2(modules []structureModule) error { // Depth 3 — Include Types and Details // ============================================================================ -func (e *Executor) structureDepth3(modules []structureModule) error { +func structureDepth3(ctx *ExecContext, modules []structureModule) error { + e := ctx.executor // Same data loading as depth 2 h, err := e.getHierarchy() if err != nil { @@ -548,12 +559,12 @@ func (e *Executor) structureDepth3(modules []structureModule) error { for i, m := range modules { if i > 0 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } - fmt.Fprintln(e.output, m.Name) + fmt.Fprintln(ctx.Output, m.Name) // Entities (with types) - e.structureEntities(m.Name, dmByModule[m.Name], true) + structureEntities(ctx, m.Name, dmByModule[m.Name], true) // Enumerations if enums, ok := enumsByModule[m.Name]; ok { @@ -563,7 +574,7 @@ func (e *Executor) structureDepth3(modules []structureModule) error { for i, v := range enum.Values { values[i] = v.Name } - fmt.Fprintf(e.output, " Enumeration %s.%s [%s]\n", m.Name, enum.Name, strings.Join(values, ", ")) + fmt.Fprintf(ctx.Output, " Enumeration %s.%s [%s]\n", m.Name, enum.Name, strings.Join(values, ", ")) } } @@ -571,7 +582,7 @@ func (e *Executor) structureDepth3(modules []structureModule) error { if mfs, ok := mfByModule[m.Name]; ok { sortMicroflows(mfs) for _, mf := range mfs { - fmt.Fprintf(e.output, " Microflow %s.%s%s\n", m.Name, mf.Name, formatMicroflowSignature(mf.Parameters, mf.ReturnType, true)) + fmt.Fprintf(ctx.Output, " Microflow %s.%s%s\n", m.Name, mf.Name, formatMicroflowSignature(mf.Parameters, mf.ReturnType, true)) } } @@ -579,21 +590,21 @@ func (e *Executor) structureDepth3(modules []structureModule) error { if nfs, ok := nfByModule[m.Name]; ok { sortNanoflows(nfs) for _, nf := range nfs { - fmt.Fprintf(e.output, " Nanoflow %s.%s%s\n", m.Name, nf.Name, formatMicroflowSignature(nf.Parameters, nf.ReturnType, true)) + fmt.Fprintf(ctx.Output, " Nanoflow %s.%s%s\n", m.Name, nf.Name, formatMicroflowSignature(nf.Parameters, nf.ReturnType, true)) } } // Workflows (with details) - e.structureWorkflows(m.Name, wfByModule[m.Name], true) + structureWorkflows(ctx, m.Name, wfByModule[m.Name], true) // Pages - e.structurePages(m.Name) + structurePages(ctx, m.Name) // Snippets - e.structureSnippets(m.Name) + structureSnippets(ctx, m.Name) // Java Actions (with param names) - e.outputJavaActions(m.Name, jaByModule[m.Name], true) + outputJavaActions(ctx, m.Name, jaByModule[m.Name], true) // Constants (with default value) if consts, ok := constByModule[m.Name]; ok { @@ -603,7 +614,7 @@ func (e *Executor) structureDepth3(modules []structureModule) error { if c.DefaultValue != "" { s += " = " + c.DefaultValue } - fmt.Fprintln(e.output, s) + fmt.Fprintln(ctx.Output, s) } } @@ -611,16 +622,16 @@ func (e *Executor) structureDepth3(modules []structureModule) error { if events, ok := eventsByModule[m.Name]; ok { sortScheduledEvents(events) for _, ev := range events { - fmt.Fprintf(e.output, " ScheduledEvent %s.%s\n", m.Name, ev.Name) + fmt.Fprintf(ctx.Output, " ScheduledEvent %s.%s\n", m.Name, ev.Name) } } // OData - e.structureODataClients(m.Name) - e.structureODataServices(m.Name) + structureODataClients(ctx, m.Name) + structureODataServices(ctx, m.Name) // Business Event Services - e.structureBusinessEventServices(m.Name) + structureBusinessEventServices(ctx, m.Name) } return nil @@ -631,7 +642,7 @@ func (e *Executor) structureDepth3(modules []structureModule) error { // ============================================================================ // structureEntities outputs entities for a module. -func (e *Executor) structureEntities(moduleName string, dm *domainmodel.DomainModel, withTypes bool) { +func structureEntities(ctx *ExecContext, moduleName string, dm *domainmodel.DomainModel, withTypes bool) { if dm == nil { return } @@ -667,9 +678,9 @@ func (e *Executor) structureEntities(moduleName string, dm *domainmodel.DomainMo } qualName := moduleName + "." + ent.Name if len(attrParts) > 0 { - fmt.Fprintf(e.output, " Entity %s [%s]\n", qualName, strings.Join(attrParts, ", ")) + fmt.Fprintf(ctx.Output, " Entity %s [%s]\n", qualName, strings.Join(attrParts, ", ")) } else { - fmt.Fprintf(e.output, " Entity %s\n", qualName) + fmt.Fprintf(ctx.Output, " Entity %s\n", qualName) } // Format associations (owned by this entity) @@ -696,16 +707,16 @@ func (e *Executor) structureEntities(moduleName string, dm *domainmodel.DomainMo assocParts = append(assocParts, part) } if len(assocParts) > 0 { - fmt.Fprintf(e.output, " %s\n", strings.Join(assocParts, ", ")) + fmt.Fprintf(ctx.Output, " %s\n", strings.Join(assocParts, ", ")) } } } } // structurePages outputs pages for a module from the catalog. -func (e *Executor) structurePages(moduleName string) { +func structurePages(ctx *ExecContext, moduleName string) { // Query pages from catalog - result, err := e.catalog.Query(fmt.Sprintf( + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name FROM pages WHERE ModuleName = '%s' ORDER BY Name", escapeSQLString(moduleName))) if err != nil || len(result.Rows) == 0 { @@ -714,7 +725,7 @@ func (e *Executor) structurePages(moduleName string) { // Try to get top-level data widgets from widgets table widgetsByPage := make(map[string][]string) - widgetResult, err := e.catalog.Query(fmt.Sprintf( + widgetResult, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT ContainerQualifiedName, WidgetType, EntityRef FROM widgets WHERE ModuleName = '%s' AND ParentWidget = '' ORDER BY ContainerQualifiedName, WidgetType", escapeSQLString(moduleName))) if err == nil { @@ -744,16 +755,16 @@ func (e *Executor) structurePages(moduleName string) { name := asString(row[0]) qualName := moduleName + "." + name if widgets, ok := widgetsByPage[qualName]; ok && len(widgets) > 0 { - fmt.Fprintf(e.output, " Page %s [%s]\n", qualName, strings.Join(widgets, ", ")) + fmt.Fprintf(ctx.Output, " Page %s [%s]\n", qualName, strings.Join(widgets, ", ")) } else { - fmt.Fprintf(e.output, " Page %s\n", qualName) + fmt.Fprintf(ctx.Output, " Page %s\n", qualName) } } } // structureSnippets outputs snippets for a module from the catalog. -func (e *Executor) structureSnippets(moduleName string) { - result, err := e.catalog.Query(fmt.Sprintf( +func structureSnippets(ctx *ExecContext, moduleName string) { + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name FROM snippets WHERE ModuleName = '%s' ORDER BY Name", escapeSQLString(moduleName))) if err != nil || len(result.Rows) == 0 { @@ -762,12 +773,12 @@ func (e *Executor) structureSnippets(moduleName string) { for _, row := range result.Rows { name := asString(row[0]) - fmt.Fprintf(e.output, " Snippet %s.%s\n", moduleName, name) + fmt.Fprintf(ctx.Output, " Snippet %s.%s\n", moduleName, name) } } // outputJavaActions outputs java actions for a module. -func (e *Executor) outputJavaActions(moduleName string, actions []*javaactions.JavaAction, withNames bool) { +func outputJavaActions(ctx *ExecContext, moduleName string, actions []*javaactions.JavaAction, withNames bool) { if len(actions) == 0 { return } @@ -781,7 +792,7 @@ func (e *Executor) outputJavaActions(moduleName string, actions []*javaactions.J for _, ja := range sorted { sig := formatJavaActionSignature(ja, withNames) - fmt.Fprintf(e.output, " JavaAction %s.%s%s\n", moduleName, ja.Name, sig) + fmt.Fprintf(ctx.Output, " JavaAction %s.%s%s\n", moduleName, ja.Name, sig) } } @@ -828,8 +839,8 @@ func formatJATypeDisplay(typeStr string) string { } // structureODataClients outputs OData clients for a module. -func (e *Executor) structureODataClients(moduleName string) { - result, err := e.catalog.Query(fmt.Sprintf( +func structureODataClients(ctx *ExecContext, moduleName string) { + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ODataVersion FROM odata_clients WHERE ModuleName = '%s' ORDER BY Name", escapeSQLString(moduleName))) if err != nil || len(result.Rows) == 0 { @@ -841,16 +852,16 @@ func (e *Executor) structureODataClients(moduleName string) { version := asString(row[1]) qualName := moduleName + "." + name if version != "" { - fmt.Fprintf(e.output, " ODataClient %s (%s)\n", qualName, version) + fmt.Fprintf(ctx.Output, " ODataClient %s (%s)\n", qualName, version) } else { - fmt.Fprintf(e.output, " ODataClient %s\n", qualName) + fmt.Fprintf(ctx.Output, " ODataClient %s\n", qualName) } } } // structureODataServices outputs OData services for a module. -func (e *Executor) structureODataServices(moduleName string) { - result, err := e.catalog.Query(fmt.Sprintf( +func structureODataServices(ctx *ExecContext, moduleName string) { + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, Path, EntitySetCount FROM odata_services WHERE ModuleName = '%s' ORDER BY Name", escapeSQLString(moduleName))) if err != nil || len(result.Rows) == 0 { @@ -863,16 +874,16 @@ func (e *Executor) structureODataServices(moduleName string) { entitySetCount := toInt(row[2]) qualName := moduleName + "." + name if path != "" { - fmt.Fprintf(e.output, " ODataService %s %s (%s)\n", qualName, path, pluralize(entitySetCount, "entity", "entities")) + fmt.Fprintf(ctx.Output, " ODataService %s %s (%s)\n", qualName, path, pluralize(entitySetCount, "entity", "entities")) } else { - fmt.Fprintf(e.output, " ODataService %s\n", qualName) + fmt.Fprintf(ctx.Output, " ODataService %s\n", qualName) } } } // structureBusinessEventServices outputs business event services for a module. -func (e *Executor) structureBusinessEventServices(moduleName string) { - result, err := e.catalog.Query(fmt.Sprintf( +func structureBusinessEventServices(ctx *ExecContext, moduleName string) { + result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, MessageCount, PublishCount, SubscribeCount FROM business_event_services WHERE ModuleName = '%s' ORDER BY Name", escapeSQLString(moduleName))) if err != nil || len(result.Rows) == 0 { @@ -898,15 +909,15 @@ func (e *Executor) structureBusinessEventServices(moduleName string) { } if len(parts) > 0 { - fmt.Fprintf(e.output, " BusinessEventService %s (%s)\n", qualName, strings.Join(parts, ", ")) + fmt.Fprintf(ctx.Output, " BusinessEventService %s (%s)\n", qualName, strings.Join(parts, ", ")) } else { - fmt.Fprintf(e.output, " BusinessEventService %s\n", qualName) + fmt.Fprintf(ctx.Output, " BusinessEventService %s\n", qualName) } } } // structureWorkflows outputs workflows for a module. -func (e *Executor) structureWorkflows(moduleName string, wfs []*workflows.Workflow, withDetails bool) { +func structureWorkflows(ctx *ExecContext, moduleName string, wfs []*workflows.Workflow, withDetails bool) { if len(wfs) == 0 { return } @@ -940,9 +951,9 @@ func (e *Executor) structureWorkflows(moduleName string, wfs []*workflows.Workfl } if len(parts) > 0 { - fmt.Fprintf(e.output, " Workflow %s (%s)\n", qualName, strings.Join(parts, ", ")) + fmt.Fprintf(ctx.Output, " Workflow %s (%s)\n", qualName, strings.Join(parts, ", ")) } else { - fmt.Fprintf(e.output, " Workflow %s\n", qualName) + fmt.Fprintf(ctx.Output, " Workflow %s\n", qualName) } } } diff --git a/mdl/executor/cmd_styling.go b/mdl/executor/cmd_styling.go index 2d7de41b..434cba99 100644 --- a/mdl/executor/cmd_styling.go +++ b/mdl/executor/cmd_styling.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "path/filepath" "reflect" @@ -19,7 +20,9 @@ import ( // SHOW DESIGN PROPERTIES // ============================================================================ -func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) error { +func execShowDesignProperties(ctx *ExecContext, s *ast.ShowDesignPropertiesStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -31,7 +34,7 @@ func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) err } if len(registry.WidgetProperties) == 0 { - fmt.Fprintln(e.output, "No design properties found. Check that themesource/*/web/design-properties.json exists in the project directory.") + fmt.Fprintln(ctx.Output, "No design properties found. Check that themesource/*/web/design-properties.json exists in the project directory.") return nil } @@ -40,11 +43,11 @@ func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) err dpKey := resolveDesignPropsKey(s.WidgetType) props := registry.GetPropertiesForWidget(dpKey) if len(props) == 0 { - fmt.Fprintf(e.output, "No design properties found for widget type %s (%s)\n", s.WidgetType, dpKey) + fmt.Fprintf(ctx.Output, "No design properties found for widget type %s (%s)\n", s.WidgetType, dpKey) return nil } - fmt.Fprintf(e.output, "Design Properties for %s:\n\n", s.WidgetType) - e.printDesignProperties(registry, dpKey) + fmt.Fprintf(ctx.Output, "Design Properties for %s:\n\n", s.WidgetType) + printDesignProperties(ctx, registry, dpKey) } else { // Show all widget types and their properties keys := make([]string, 0, len(registry.WidgetProperties)) @@ -58,51 +61,56 @@ func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) err if len(props) == 0 { continue } - fmt.Fprintf(e.output, "=== %s ===\n", key) + fmt.Fprintf(ctx.Output, "=== %s ===\n", key) for _, p := range props { - e.printOneProperty(p) + printOneProperty(ctx, p) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } } return nil } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) error { + return execShowDesignProperties(e.newExecContext(context.Background()), s) +} + // printDesignProperties prints properties for a widget type, showing inherited "Widget" props separately. -func (e *Executor) printDesignProperties(registry *ThemeRegistry, dpKey string) { +func printDesignProperties(ctx *ExecContext, registry *ThemeRegistry, dpKey string) { // Print inherited Widget properties if widgetProps, ok := registry.WidgetProperties["Widget"]; ok && len(widgetProps) > 0 { - fmt.Fprintf(e.output, "From: Widget (inherited)\n") + fmt.Fprintf(ctx.Output, "From: Widget (inherited)\n") for _, p := range widgetProps { - e.printOneProperty(p) + printOneProperty(ctx, p) } } // Print type-specific properties if dpKey != "Widget" { if typeProps, ok := registry.WidgetProperties[dpKey]; ok && len(typeProps) > 0 { - fmt.Fprintf(e.output, "From: %s\n", dpKey) + fmt.Fprintf(ctx.Output, "From: %s\n", dpKey) for _, p := range typeProps { - e.printOneProperty(p) + printOneProperty(ctx, p) } } } } // printOneProperty prints a single design property in a readable format. -func (e *Executor) printOneProperty(p ThemeProperty) { +func printOneProperty(ctx *ExecContext, p ThemeProperty) { switch p.Type { case "Toggle": - fmt.Fprintf(e.output, " %-24s Toggle class: %s\n", p.Name, p.Class) + fmt.Fprintf(ctx.Output, " %-24s Toggle class: %s\n", p.Name, p.Class) case "Dropdown", "ColorPicker", "ToggleButtonGroup": options := make([]string, 0, len(p.Options)) for _, o := range p.Options { options = append(options, o.Name) } - fmt.Fprintf(e.output, " %-24s %-11s [%s]\n", p.Name, p.Type, strings.Join(options, ", ")) + fmt.Fprintf(ctx.Output, " %-24s %-11s [%s]\n", p.Name, p.Type, strings.Join(options, ", ")) default: - fmt.Fprintf(e.output, " %-24s %s\n", p.Name, p.Type) + fmt.Fprintf(ctx.Output, " %-24s %s\n", p.Name, p.Type) } } @@ -110,7 +118,9 @@ func (e *Executor) printOneProperty(p ThemeProperty) { // DESCRIBE STYLING // ============================================================================ -func (e *Executor) execDescribeStyling(s *ast.DescribeStylingStmt) error { +func execDescribeStyling(ctx *ExecContext, s *ast.DescribeStylingStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -165,7 +175,7 @@ func (e *Executor) execDescribeStyling(s *ast.DescribeStylingStmt) error { } if len(rawWidgets) == 0 { - fmt.Fprintf(e.output, "No widgets found in %s %s\n", s.ContainerType, s.ContainerName.String()) + fmt.Fprintf(ctx.Output, "No widgets found in %s %s\n", s.ContainerType, s.ContainerName.String()) return nil } @@ -176,42 +186,47 @@ func (e *Executor) execDescribeStyling(s *ast.DescribeStylingStmt) error { if s.WidgetName != "" { return mdlerrors.NewNotFoundMsg("widget", s.WidgetName, fmt.Sprintf("widget %q not found in %s %s", s.WidgetName, s.ContainerType, s.ContainerName.String())) } - fmt.Fprintf(e.output, "No styled widgets found in %s %s\n", s.ContainerType, s.ContainerName.String()) + fmt.Fprintf(ctx.Output, "No styled widgets found in %s %s\n", s.ContainerType, s.ContainerName.String()) return nil } // Output for i, w := range styledWidgets { if i > 0 { - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } displayName := getWidgetDisplayName(w.Type) - fmt.Fprintf(e.output, "WIDGET %s (%s)\n", w.Name, displayName) + fmt.Fprintf(ctx.Output, "WIDGET %s (%s)\n", w.Name, displayName) if w.Class != "" { - fmt.Fprintf(e.output, " Class: '%s'\n", w.Class) + fmt.Fprintf(ctx.Output, " Class: '%s'\n", w.Class) } if w.Style != "" { - fmt.Fprintf(e.output, " Style: '%s'\n", w.Style) + fmt.Fprintf(ctx.Output, " Style: '%s'\n", w.Style) } if len(w.DesignProperties) > 0 { - fmt.Fprintf(e.output, " DesignProperties: [") + fmt.Fprintf(ctx.Output, " DesignProperties: [") for j, dp := range w.DesignProperties { if j > 0 { - fmt.Fprint(e.output, ", ") + fmt.Fprint(ctx.Output, ", ") } if dp.ValueType == "toggle" { - fmt.Fprintf(e.output, "'%s': ON", dp.Key) + fmt.Fprintf(ctx.Output, "'%s': ON", dp.Key) } else { - fmt.Fprintf(e.output, "'%s': '%s'", dp.Key, dp.Option) + fmt.Fprintf(ctx.Output, "'%s': '%s'", dp.Key, dp.Option) } } - fmt.Fprintln(e.output, "]") + fmt.Fprintln(ctx.Output, "]") } } return nil } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execDescribeStyling(s *ast.DescribeStylingStmt) error { + return execDescribeStyling(e.newExecContext(context.Background()), s) +} + // collectStyledWidgets walks rawWidget tree and collects widgets that have styling. // If widgetName is set, only returns the widget matching that name. func collectStyledWidgets(widgets []rawWidget, widgetName string) []rawWidget { @@ -252,7 +267,9 @@ func collectStyledWidgets(widgets []rawWidget, widgetName string) []rawWidget { // ALTER STYLING // ============================================================================ -func (e *Executor) execAlterStyling(s *ast.AlterStylingStmt) error { +func execAlterStyling(ctx *ExecContext, s *ast.AlterStylingStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -266,15 +283,22 @@ func (e *Executor) execAlterStyling(s *ast.AlterStylingStmt) error { } if s.ContainerType == "PAGE" { - return e.alterStylingOnPage(s, h) + return alterStylingOnPage(ctx, s, h) } else if s.ContainerType == "SNIPPET" { - return e.alterStylingOnSnippet(s, h) + return alterStylingOnSnippet(ctx, s, h) } return mdlerrors.NewUnsupported("unsupported container type: " + s.ContainerType) } -func (e *Executor) alterStylingOnPage(s *ast.AlterStylingStmt, h *ContainerHierarchy) error { +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execAlterStyling(s *ast.AlterStylingStmt) error { + return execAlterStyling(e.newExecContext(context.Background()), s) +} + +func alterStylingOnPage(ctx *ExecContext, s *ast.AlterStylingStmt, h *ContainerHierarchy) error { + e := ctx.executor + // Find page allPages, err := e.reader.ListPages() if err != nil { @@ -317,11 +341,13 @@ func (e *Executor) alterStylingOnPage(s *ast.AlterStylingStmt, h *ContainerHiera return mdlerrors.NewBackend("save page", err) } - fmt.Fprintf(e.output, "Updated styling on widget %q in page %s\n", s.WidgetName, s.ContainerName.String()) + fmt.Fprintf(ctx.Output, "Updated styling on widget %q in page %s\n", s.WidgetName, s.ContainerName.String()) return nil } -func (e *Executor) alterStylingOnSnippet(s *ast.AlterStylingStmt, h *ContainerHierarchy) error { +func alterStylingOnSnippet(ctx *ExecContext, s *ast.AlterStylingStmt, h *ContainerHierarchy) error { + e := ctx.executor + // Find snippet allSnippets, err := e.reader.ListSnippets() if err != nil { @@ -364,7 +390,7 @@ func (e *Executor) alterStylingOnSnippet(s *ast.AlterStylingStmt, h *ContainerHi return mdlerrors.NewBackend("save snippet", err) } - fmt.Fprintf(e.output, "Updated styling on widget %q in snippet %s\n", s.WidgetName, s.ContainerName.String()) + fmt.Fprintf(ctx.Output, "Updated styling on widget %q in snippet %s\n", s.WidgetName, s.ContainerName.String()) return nil } @@ -509,7 +535,9 @@ func setDesignProperty(baseWidget reflect.Value, a ast.StylingAssignment) error } // findPageByName looks up a page by qualified name. -func (e *Executor) findPageByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Page, error) { +func findPageByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHierarchy) (*pages.Page, error) { + e := ctx.executor + allPages, err := e.reader.ListPages() if err != nil { return nil, mdlerrors.NewBackend("list pages", err) @@ -524,8 +552,15 @@ func (e *Executor) findPageByName(name ast.QualifiedName, h *ContainerHierarchy) return nil, mdlerrors.NewNotFound("page", name.String()) } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) findPageByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Page, error) { + return findPageByName(e.newExecContext(context.Background()), name, h) +} + // findSnippetByName looks up a snippet by qualified name. -func (e *Executor) findSnippetByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Snippet, model.ID, error) { +func findSnippetByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHierarchy) (*pages.Snippet, model.ID, error) { + e := ctx.executor + allSnippets, err := e.reader.ListSnippets() if err != nil { return nil, "", mdlerrors.NewBackend("list snippets", err) @@ -539,3 +574,8 @@ func (e *Executor) findSnippetByName(name ast.QualifiedName, h *ContainerHierarc } return nil, "", mdlerrors.NewNotFound("snippet", name.String()) } + +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) findSnippetByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Snippet, model.ID, error) { + return findSnippetByName(e.newExecContext(context.Background()), name, h) +} diff --git a/mdl/executor/cmd_widgets.go b/mdl/executor/cmd_widgets.go index 6fa4212b..1c93473c 100644 --- a/mdl/executor/cmd_widgets.go +++ b/mdl/executor/cmd_widgets.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -15,7 +16,9 @@ import ( ) // execShowWidgets handles the SHOW WIDGETS statement. -func (e *Executor) execShowWidgets(s *ast.ShowWidgetsStmt) error { +func execShowWidgets(ctx *ExecContext, s *ast.ShowWidgetsStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -48,21 +51,21 @@ func (e *Executor) execShowWidgets(s *ast.ShowWidgetsStmt) error { query.WriteString(" ORDER BY ModuleName, ContainerQualifiedName, Name") // Execute query using SQLite parameterization - result, err := e.executeCatalogQueryWithArgs(query.String(), args...) + result, err := executeCatalogQueryWithArgs(ctx, query.String(), args...) if err != nil { return mdlerrors.NewBackend("query widgets", err) } // Output results as table if result.Count == 0 { - fmt.Fprintln(e.output, "No widgets found matching the criteria") + fmt.Fprintln(ctx.Output, "No widgets found matching the criteria") return nil } // Print header - fmt.Fprintf(e.output, "\n%-30s %-40s %-40s %-20s\n", + fmt.Fprintf(ctx.Output, "\n%-30s %-40s %-40s %-20s\n", "NAME", "WIDGET TYPE", "CONTAINER", "MODULE") - fmt.Fprintln(e.output, strings.Repeat("-", 130)) + fmt.Fprintln(ctx.Output, strings.Repeat("-", 130)) // Print rows for _, row := range result.Rows { @@ -70,15 +73,22 @@ func (e *Executor) execShowWidgets(s *ast.ShowWidgetsStmt) error { widgetType := formatCell(row[1], 40) container := formatCell(row[2], 40) module := formatCell(row[3], 20) - fmt.Fprintf(e.output, "%-30s %-40s %-40s %-20s\n", name, widgetType, container, module) + fmt.Fprintf(ctx.Output, "%-30s %-40s %-40s %-20s\n", name, widgetType, container, module) } - fmt.Fprintf(e.output, "\n%d widget(s) found\n", result.Count) + fmt.Fprintf(ctx.Output, "\n%d widget(s) found\n", result.Count) return nil } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execShowWidgets(s *ast.ShowWidgetsStmt) error { + return execShowWidgets(e.newExecContext(context.Background()), s) +} + // execUpdateWidgets handles the UPDATE WIDGETS statement. -func (e *Executor) execUpdateWidgets(s *ast.UpdateWidgetsStmt) error { +func execUpdateWidgets(ctx *ExecContext, s *ast.UpdateWidgetsStmt) error { + e := ctx.executor + if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -92,13 +102,13 @@ func (e *Executor) execUpdateWidgets(s *ast.UpdateWidgetsStmt) error { } // Find matching widgets - widgets, err := e.findMatchingWidgets(s.Filters, s.InModule) + widgets, err := findMatchingWidgets(ctx, s.Filters, s.InModule) if err != nil { return mdlerrors.NewBackend("find widgets", err) } if len(widgets) == 0 { - fmt.Fprintln(e.output, "No widgets found matching the criteria") + fmt.Fprintln(ctx.Output, "No widgets found matching the criteria") return nil } @@ -106,35 +116,40 @@ func (e *Executor) execUpdateWidgets(s *ast.UpdateWidgetsStmt) error { containers := groupWidgetsByContainer(widgets) // Report what will be updated - fmt.Fprintf(e.output, "\nFound %d widget(s) in %d container(s) matching the criteria\n", + fmt.Fprintf(ctx.Output, "\nFound %d widget(s) in %d container(s) matching the criteria\n", len(widgets), len(containers)) if s.DryRun { - fmt.Fprintln(e.output, "\n[DRY RUN] The following changes would be made:") + fmt.Fprintln(ctx.Output, "\n[DRY RUN] The following changes would be made:") } // Process each container totalUpdated := 0 for containerID, widgetRefs := range containers { - updated, err := e.updateWidgetsInContainer(containerID, widgetRefs, s.Assignments, s.DryRun) + updated, err := updateWidgetsInContainer(ctx, containerID, widgetRefs, s.Assignments, s.DryRun) if err != nil { - fmt.Fprintf(e.output, "Warning: Failed to update widgets in %s: %v\n", containerID, err) + fmt.Fprintf(ctx.Output, "Warning: Failed to update widgets in %s: %v\n", containerID, err) continue } totalUpdated += updated } if s.DryRun { - fmt.Fprintf(e.output, "\n[DRY RUN] Would update %d widget(s)\n", totalUpdated) - fmt.Fprintln(e.output, "\nRun without DRY RUN to apply changes.") + fmt.Fprintf(ctx.Output, "\n[DRY RUN] Would update %d widget(s)\n", totalUpdated) + fmt.Fprintln(ctx.Output, "\nRun without DRY RUN to apply changes.") } else { - fmt.Fprintf(e.output, "\nUpdated %d widget(s)\n", totalUpdated) - fmt.Fprintln(e.output, "\nNote: Run 'REFRESH CATALOG FULL FORCE' to update the catalog with changes.") + fmt.Fprintf(ctx.Output, "\nUpdated %d widget(s)\n", totalUpdated) + fmt.Fprintln(ctx.Output, "\nNote: Run 'REFRESH CATALOG FULL FORCE' to update the catalog with changes.") } return nil } +// Wrapper for callers that haven't been migrated yet. +func (e *Executor) execUpdateWidgets(s *ast.UpdateWidgetsStmt) error { + return execUpdateWidgets(e.newExecContext(context.Background()), s) +} + // widgetRef holds information about a widget to be updated. type widgetRef struct { ID string @@ -146,7 +161,7 @@ type widgetRef struct { } // findMatchingWidgets queries the catalog for widgets matching the filters. -func (e *Executor) findMatchingWidgets(filters []ast.WidgetFilter, module string) ([]widgetRef, error) { +func findMatchingWidgets(ctx *ExecContext, filters []ast.WidgetFilter, module string) ([]widgetRef, error) { var query strings.Builder query.WriteString(`SELECT Id, Name, WidgetType, ContainerId, ContainerQualifiedName, ContainerType FROM widgets WHERE 1=1`) @@ -167,7 +182,7 @@ func (e *Executor) findMatchingWidgets(filters []ast.WidgetFilter, module string args = append(args, module) } - result, err := e.executeCatalogQueryWithArgs(query.String(), args...) + result, err := executeCatalogQueryWithArgs(ctx, query.String(), args...) if err != nil { return nil, err } @@ -197,7 +212,7 @@ func groupWidgetsByContainer(widgets []widgetRef) map[string][]widgetRef { } // updateWidgetsInContainer updates widgets within a single page or snippet. -func (e *Executor) updateWidgetsInContainer(containerID string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { +func updateWidgetsInContainer(ctx *ExecContext, containerID string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { if len(widgetRefs) == 0 { return 0, nil } @@ -207,16 +222,18 @@ func (e *Executor) updateWidgetsInContainer(containerID string, widgetRefs []wid // Load the page or snippet if strings.ToLower(containerType) == "page" { - return e.updateWidgetsInPage(containerID, containerName, widgetRefs, assignments, dryRun) + return updateWidgetsInPage(ctx, containerID, containerName, widgetRefs, assignments, dryRun) } else if strings.ToLower(containerType) == "snippet" { - return e.updateWidgetsInSnippet(containerID, containerName, widgetRefs, assignments, dryRun) + return updateWidgetsInSnippet(ctx, containerID, containerName, widgetRefs, assignments, dryRun) } return 0, mdlerrors.NewUnsupported(fmt.Sprintf("unsupported container type: %s", containerType)) } // updateWidgetsInPage updates widgets in a page using raw BSON. -func (e *Executor) updateWidgetsInPage(containerID, containerName string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { +func updateWidgetsInPage(ctx *ExecContext, containerID, containerName string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { + e := ctx.executor + // Load raw BSON as ordered document (preserves field ordering) rawBytes, err := e.reader.GetRawUnitBytes(model.ID(containerID)) if err != nil { @@ -231,16 +248,16 @@ func (e *Executor) updateWidgetsInPage(containerID, containerName string, widget for _, ref := range widgetRefs { result := findBsonWidget(rawData, ref.Name) if result == nil { - fmt.Fprintf(e.output, " Warning: Widget %q not found in page %s\n", ref.Name, containerName) + fmt.Fprintf(ctx.Output, " Warning: Widget %q not found in page %s\n", ref.Name, containerName) continue } for _, assignment := range assignments { if dryRun { - fmt.Fprintf(e.output, " Would set '%s' = %v on %s (%s) in %s\n", + fmt.Fprintf(ctx.Output, " Would set '%s' = %v on %s (%s) in %s\n", assignment.PropertyPath, assignment.Value, ref.Name, ref.WidgetType, containerName) } else { if err := setRawWidgetProperty(result.widget, assignment.PropertyPath, assignment.Value); err != nil { - fmt.Fprintf(e.output, " Warning: Failed to set '%s' on %s: %v\n", + fmt.Fprintf(ctx.Output, " Warning: Failed to set '%s' on %s: %v\n", assignment.PropertyPath, ref.Name, err) } } @@ -263,7 +280,9 @@ func (e *Executor) updateWidgetsInPage(containerID, containerName string, widget } // updateWidgetsInSnippet updates widgets in a snippet using raw BSON. -func (e *Executor) updateWidgetsInSnippet(containerID, containerName string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { +func updateWidgetsInSnippet(ctx *ExecContext, containerID, containerName string, widgetRefs []widgetRef, assignments []ast.WidgetPropertyAssignment, dryRun bool) (int, error) { + e := ctx.executor + // Load raw BSON as ordered document (preserves field ordering) rawBytes, err := e.reader.GetRawUnitBytes(model.ID(containerID)) if err != nil { @@ -278,16 +297,16 @@ func (e *Executor) updateWidgetsInSnippet(containerID, containerName string, wid for _, ref := range widgetRefs { result := findBsonWidgetInSnippet(rawData, ref.Name) if result == nil { - fmt.Fprintf(e.output, " Warning: Widget %q not found in snippet %s\n", ref.Name, containerName) + fmt.Fprintf(ctx.Output, " Warning: Widget %q not found in snippet %s\n", ref.Name, containerName) continue } for _, assignment := range assignments { if dryRun { - fmt.Fprintf(e.output, " Would set '%s' = %v on %s (%s) in %s\n", + fmt.Fprintf(ctx.Output, " Would set '%s' = %v on %s (%s) in %s\n", assignment.PropertyPath, assignment.Value, ref.Name, ref.WidgetType, containerName) } else { if err := setRawWidgetProperty(result.widget, assignment.PropertyPath, assignment.Value); err != nil { - fmt.Fprintf(e.output, " Warning: Failed to set '%s' on %s: %v\n", + fmt.Fprintf(ctx.Output, " Warning: Failed to set '%s' on %s: %v\n", assignment.PropertyPath, ref.Name, err) } } @@ -326,7 +345,7 @@ func mapWidgetFilterField(field string) string { } // executeCatalogQueryWithArgs executes a parameterized SQL query against the catalog. -func (e *Executor) executeCatalogQueryWithArgs(query string, args ...any) (*catalogQueryResult, error) { +func executeCatalogQueryWithArgs(ctx *ExecContext, query string, args ...any) (*catalogQueryResult, error) { // Replace ? placeholders with values for SQLite // Note: This is a simplified implementation; production code should use prepared statements finalQuery := query @@ -337,7 +356,7 @@ func (e *Executor) executeCatalogQueryWithArgs(query string, args ...any) (*cata finalQuery = strings.Replace(finalQuery, "?", fmt.Sprintf("'%s'", strVal), 1) } - result, err := e.catalog.Query(finalQuery) + result, err := ctx.Catalog.Query(finalQuery) if err != nil { return nil, err } diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 3863748b..35dfc38c 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -113,16 +113,16 @@ func registerPageHandlers(r *Registry) { return execCreateJavaAction(ctx, stmt.(*ast.CreateJavaActionStmt)) }) r.Register(&ast.DropFolderStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDropFolder(stmt.(*ast.DropFolderStmt)) + return execDropFolder(ctx, stmt.(*ast.DropFolderStmt)) }) r.Register(&ast.MoveFolderStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execMoveFolder(stmt.(*ast.MoveFolderStmt)) + return execMoveFolder(ctx, stmt.(*ast.MoveFolderStmt)) }) r.Register(&ast.MoveStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execMove(stmt.(*ast.MoveStmt)) + return execMove(ctx, stmt.(*ast.MoveStmt)) }) r.Register(&ast.RenameStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRename(stmt.(*ast.RenameStmt)) + return execRename(ctx, stmt.(*ast.RenameStmt)) }) } @@ -293,7 +293,7 @@ func registerRESTHandlers(r *Registry) { return execCreateExternalEntity(ctx, stmt.(*ast.CreateExternalEntityStmt)) }) r.Register(&ast.CreateExternalEntitiesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.createExternalEntities(stmt.(*ast.CreateExternalEntitiesStmt)) + return createExternalEntities(ctx, stmt.(*ast.CreateExternalEntitiesStmt)) }) r.Register(&ast.GrantODataServiceAccessStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return execGrantODataServiceAccess(ctx, stmt.(*ast.GrantODataServiceAccessStmt)) @@ -323,10 +323,10 @@ func registerQueryHandlers(r *Registry) { return ctx.executor.execShow(stmt.(*ast.ShowStmt)) }) r.Register(&ast.ShowWidgetsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execShowWidgets(stmt.(*ast.ShowWidgetsStmt)) + return execShowWidgets(ctx, stmt.(*ast.ShowWidgetsStmt)) }) r.Register(&ast.UpdateWidgetsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execUpdateWidgets(stmt.(*ast.UpdateWidgetsStmt)) + return execUpdateWidgets(ctx, stmt.(*ast.UpdateWidgetsStmt)) }) r.Register(&ast.SelectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return ctx.executor.execCatalogQuery(stmt.(*ast.SelectStmt).Query) @@ -340,28 +340,28 @@ func registerQueryHandlers(r *Registry) { // NOTE: ShowFeaturesStmt was missing from the original type-switch // (pre-existing bug). Adding it here to fix the dead-code path. r.Register(&ast.ShowFeaturesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execShowFeatures(stmt.(*ast.ShowFeaturesStmt)) + return execShowFeatures(ctx, stmt.(*ast.ShowFeaturesStmt)) }) } func registerStylingHandlers(r *Registry) { r.Register(&ast.ShowDesignPropertiesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execShowDesignProperties(stmt.(*ast.ShowDesignPropertiesStmt)) + return execShowDesignProperties(ctx, stmt.(*ast.ShowDesignPropertiesStmt)) }) r.Register(&ast.DescribeStylingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDescribeStyling(stmt.(*ast.DescribeStylingStmt)) + return execDescribeStyling(ctx, stmt.(*ast.DescribeStylingStmt)) }) r.Register(&ast.AlterStylingStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execAlterStyling(stmt.(*ast.AlterStylingStmt)) + return execAlterStyling(ctx, stmt.(*ast.AlterStylingStmt)) }) } func registerRepositoryHandlers(r *Registry) { r.Register(&ast.UpdateStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execUpdate() + return execUpdate(ctx) }) r.Register(&ast.RefreshStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRefresh() + return execRefresh(ctx) }) r.Register(&ast.RefreshCatalogStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return ctx.executor.execRefreshCatalogStmt(stmt.(*ast.RefreshCatalogStmt)) @@ -373,22 +373,22 @@ func registerRepositoryHandlers(r *Registry) { func registerSessionHandlers(r *Registry) { r.Register(&ast.SetStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSet(stmt.(*ast.SetStmt)) + return execSet(ctx, stmt.(*ast.SetStmt)) }) r.Register(&ast.HelpStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execHelp() + return execHelp(ctx) }) r.Register(&ast.ExitStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execExit() + return execExit(ctx) }) r.Register(&ast.ExecuteScriptStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execExecuteScript(stmt.(*ast.ExecuteScriptStmt)) + return execExecuteScript(ctx, stmt.(*ast.ExecuteScriptStmt)) }) } func registerLintHandlers(r *Registry) { r.Register(&ast.LintStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execLint(stmt.(*ast.LintStmt)) + return execLint(ctx, stmt.(*ast.LintStmt)) }) } @@ -400,45 +400,45 @@ func registerAlterPageHandlers(r *Registry) { func registerFragmentHandlers(r *Registry) { r.Register(&ast.DefineFragmentStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDefineFragment(stmt.(*ast.DefineFragmentStmt)) + return execDefineFragment(ctx, stmt.(*ast.DefineFragmentStmt)) }) r.Register(&ast.DescribeFragmentFromStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.describeFragmentFrom(stmt.(*ast.DescribeFragmentFromStmt)) + return describeFragmentFrom(ctx, stmt.(*ast.DescribeFragmentFromStmt)) }) } func registerSQLHandlers(r *Registry) { r.Register(&ast.SQLConnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLConnect(stmt.(*ast.SQLConnectStmt)) + return execSQLConnect(ctx, stmt.(*ast.SQLConnectStmt)) }) r.Register(&ast.SQLDisconnectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLDisconnect(stmt.(*ast.SQLDisconnectStmt)) + return execSQLDisconnect(ctx, stmt.(*ast.SQLDisconnectStmt)) }) r.Register(&ast.SQLConnectionsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLConnections() + return execSQLConnections(ctx) }) r.Register(&ast.SQLQueryStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLQuery(stmt.(*ast.SQLQueryStmt)) + return execSQLQuery(ctx, stmt.(*ast.SQLQueryStmt)) }) r.Register(&ast.SQLShowTablesStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLShowTables(stmt.(*ast.SQLShowTablesStmt)) + return execSQLShowTables(ctx, stmt.(*ast.SQLShowTablesStmt)) }) r.Register(&ast.SQLShowViewsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLShowViews(stmt.(*ast.SQLShowViewsStmt)) + return execSQLShowViews(ctx, stmt.(*ast.SQLShowViewsStmt)) }) r.Register(&ast.SQLShowFunctionsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLShowFunctions(stmt.(*ast.SQLShowFunctionsStmt)) + return execSQLShowFunctions(ctx, stmt.(*ast.SQLShowFunctionsStmt)) }) r.Register(&ast.SQLDescribeTableStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLDescribeTable(stmt.(*ast.SQLDescribeTableStmt)) + return execSQLDescribeTable(ctx, stmt.(*ast.SQLDescribeTableStmt)) }) r.Register(&ast.SQLGenerateConnectorStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSQLGenerateConnector(stmt.(*ast.SQLGenerateConnectorStmt)) + return execSQLGenerateConnector(ctx, stmt.(*ast.SQLGenerateConnectorStmt)) }) } func registerImportHandlers(r *Registry) { r.Register(&ast.ImportStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execImport(stmt.(*ast.ImportStmt)) + return execImport(ctx, stmt.(*ast.ImportStmt)) }) } From a1cdedd8c02fe3890076f4a08c2340d6ccd9792f Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 17:36:37 +0200 Subject: [PATCH 13/16] refactor: migrate utility and core functions to free functions Migrate helpers, format, hierarchy, autocomplete, validate, oql_type_inference, cmd_catalog, executor_query, and remaining cmd_modules methods from Executor methods to free functions. ~73 methods converted across 72 files. All handler and utility methods now use *ExecContext. Only Executor lifecycle methods (Execute, ExecuteProgram, SetQuiet, Close, etc.) remain as receiver methods. --- mdl/executor/autocomplete.go | 184 ++++++++--- mdl/executor/cmd_agenteditor_agents.go | 8 +- mdl/executor/cmd_agenteditor_kbs.go | 8 +- mdl/executor/cmd_agenteditor_mcpservices.go | 8 +- mdl/executor/cmd_agenteditor_models.go | 8 +- mdl/executor/cmd_alter_page.go | 2 +- mdl/executor/cmd_alter_workflow.go | 4 +- mdl/executor/cmd_associations.go | 22 +- mdl/executor/cmd_businessevents.go | 18 +- mdl/executor/cmd_catalog.go | 329 ++++++++++--------- mdl/executor/cmd_constants.go | 28 +- mdl/executor/cmd_context.go | 30 +- mdl/executor/cmd_contract.go | 18 +- mdl/executor/cmd_datatransformer.go | 10 +- mdl/executor/cmd_dbconnection.go | 14 +- mdl/executor/cmd_diff.go | 10 +- mdl/executor/cmd_diff_local.go | 5 +- mdl/executor/cmd_domainmodel_elk.go | 6 +- mdl/executor/cmd_entities.go | 56 ++-- mdl/executor/cmd_entities_access.go | 4 +- mdl/executor/cmd_entities_describe.go | 10 +- mdl/executor/cmd_enumerations.go | 14 +- mdl/executor/cmd_export_mappings.go | 8 +- mdl/executor/cmd_features.go | 12 +- mdl/executor/cmd_folders.go | 12 +- mdl/executor/cmd_fragments.go | 2 +- mdl/executor/cmd_imagecollections.go | 10 +- mdl/executor/cmd_import.go | 2 +- mdl/executor/cmd_import_mappings.go | 8 +- mdl/executor/cmd_javaactions.go | 8 +- mdl/executor/cmd_javascript_actions.go | 4 +- mdl/executor/cmd_jsonstructures.go | 15 +- mdl/executor/cmd_languages.go | 3 +- mdl/executor/cmd_layouts.go | 4 +- mdl/executor/cmd_lint.go | 2 +- mdl/executor/cmd_mermaid.go | 8 +- mdl/executor/cmd_microflow_elk.go | 2 +- mdl/executor/cmd_microflows_create.go | 8 +- mdl/executor/cmd_microflows_drop.go | 4 +- mdl/executor/cmd_microflows_format_action.go | 2 +- mdl/executor/cmd_microflows_show.go | 14 +- mdl/executor/cmd_module_overview.go | 2 +- mdl/executor/cmd_modules.go | 118 ++++--- mdl/executor/cmd_move.go | 18 +- mdl/executor/cmd_navigation.go | 2 +- mdl/executor/cmd_odata.go | 60 ++-- mdl/executor/cmd_page_wireframe.go | 4 +- mdl/executor/cmd_pages_builder.go | 6 +- mdl/executor/cmd_pages_create_v3.go | 8 +- mdl/executor/cmd_pages_describe.go | 8 +- mdl/executor/cmd_pages_describe_output.go | 2 +- mdl/executor/cmd_pages_show.go | 4 +- mdl/executor/cmd_published_rest.go | 14 +- mdl/executor/cmd_rename.go | 28 +- mdl/executor/cmd_rest_clients.go | 14 +- mdl/executor/cmd_search.go | 20 +- mdl/executor/cmd_security.go | 22 +- mdl/executor/cmd_security_write.go | 30 +- mdl/executor/cmd_settings.go | 2 +- mdl/executor/cmd_snippets.go | 4 +- mdl/executor/cmd_structure.go | 11 +- mdl/executor/cmd_styling.go | 4 +- mdl/executor/cmd_widgets.go | 4 +- mdl/executor/cmd_workflows.go | 6 +- mdl/executor/cmd_workflows_write.go | 10 +- mdl/executor/executor_query.go | 208 ++++++------ mdl/executor/format.go | 71 ++-- mdl/executor/helpers.go | 155 ++++++--- mdl/executor/hierarchy.go | 43 ++- mdl/executor/oql_type_inference.go | 52 +-- mdl/executor/register_stubs.go | 12 +- mdl/executor/validate.go | 82 +++-- 72 files changed, 1111 insertions(+), 837 deletions(-) diff --git a/mdl/executor/autocomplete.go b/mdl/executor/autocomplete.go index 2aa60537..c106c1fd 100644 --- a/mdl/executor/autocomplete.go +++ b/mdl/executor/autocomplete.go @@ -4,8 +4,11 @@ // Returns qualified names for modules, entities, microflows, pages, etc. package executor -// GetModuleNames returns a list of all module names for autocomplete. -func (e *Executor) GetModuleNames() []string { +import "context" + +// getModuleNames returns a list of all module names for autocomplete. +func getModuleNames(ctx *ExecContext) []string { + e := ctx.executor if e.reader == nil { return nil } @@ -20,12 +23,13 @@ func (e *Executor) GetModuleNames() []string { return names } -// GetMicroflowNames returns qualified microflow names, optionally filtered by module. -func (e *Executor) GetMicroflowNames(moduleFilter string) []string { +// getMicroflowNamesAC returns qualified microflow names, optionally filtered by module. +func getMicroflowNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -44,12 +48,13 @@ func (e *Executor) GetMicroflowNames(moduleFilter string) []string { return names } -// GetEntityNames returns qualified entity names, optionally filtered by module. -func (e *Executor) GetEntityNames(moduleFilter string) []string { +// getEntityNamesAC returns qualified entity names, optionally filtered by module. +func getEntityNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -70,12 +75,13 @@ func (e *Executor) GetEntityNames(moduleFilter string) []string { return names } -// GetPageNames returns qualified page names, optionally filtered by module. -func (e *Executor) GetPageNames(moduleFilter string) []string { +// getPageNamesAC returns qualified page names, optionally filtered by module. +func getPageNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -94,12 +100,13 @@ func (e *Executor) GetPageNames(moduleFilter string) []string { return names } -// GetSnippetNames returns qualified snippet names, optionally filtered by module. -func (e *Executor) GetSnippetNames(moduleFilter string) []string { +// getSnippetNamesAC returns qualified snippet names, optionally filtered by module. +func getSnippetNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -118,12 +125,13 @@ func (e *Executor) GetSnippetNames(moduleFilter string) []string { return names } -// GetAssociationNames returns qualified association names, optionally filtered by module. -func (e *Executor) GetAssociationNames(moduleFilter string) []string { +// getAssociationNamesAC returns qualified association names, optionally filtered by module. +func getAssociationNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -144,12 +152,13 @@ func (e *Executor) GetAssociationNames(moduleFilter string) []string { return names } -// GetEnumerationNames returns qualified enumeration names, optionally filtered by module. -func (e *Executor) GetEnumerationNames(moduleFilter string) []string { +// getEnumerationNamesAC returns qualified enumeration names, optionally filtered by module. +func getEnumerationNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -168,12 +177,13 @@ func (e *Executor) GetEnumerationNames(moduleFilter string) []string { return names } -// GetLayoutNames returns qualified layout names, optionally filtered by module. -func (e *Executor) GetLayoutNames(moduleFilter string) []string { +// getLayoutNamesAC returns qualified layout names, optionally filtered by module. +func getLayoutNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -192,12 +202,13 @@ func (e *Executor) GetLayoutNames(moduleFilter string) []string { return names } -// GetJavaActionNames returns qualified Java action names, optionally filtered by module. -func (e *Executor) GetJavaActionNames(moduleFilter string) []string { +// getJavaActionNamesAC returns qualified Java action names, optionally filtered by module. +func getJavaActionNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -216,12 +227,13 @@ func (e *Executor) GetJavaActionNames(moduleFilter string) []string { return names } -// GetODataClientNames returns qualified consumed OData service names, optionally filtered by module. -func (e *Executor) GetODataClientNames(moduleFilter string) []string { +// getODataClientNamesAC returns qualified consumed OData service names, optionally filtered by module. +func getODataClientNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -240,12 +252,13 @@ func (e *Executor) GetODataClientNames(moduleFilter string) []string { return names } -// GetODataServiceNames returns qualified published OData service names, optionally filtered by module. -func (e *Executor) GetODataServiceNames(moduleFilter string) []string { +// getODataServiceNamesAC returns qualified published OData service names, optionally filtered by module. +func getODataServiceNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -264,12 +277,13 @@ func (e *Executor) GetODataServiceNames(moduleFilter string) []string { return names } -// GetRestClientNames returns qualified consumed REST service names, optionally filtered by module. -func (e *Executor) GetRestClientNames(moduleFilter string) []string { +// getRestClientNamesAC returns qualified consumed REST service names, optionally filtered by module. +func getRestClientNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -288,12 +302,13 @@ func (e *Executor) GetRestClientNames(moduleFilter string) []string { return names } -// GetDatabaseConnectionNames returns qualified database connection names, optionally filtered by module. -func (e *Executor) GetDatabaseConnectionNames(moduleFilter string) []string { +// getDatabaseConnectionNamesAC returns qualified database connection names, optionally filtered by module. +func getDatabaseConnectionNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -312,12 +327,13 @@ func (e *Executor) GetDatabaseConnectionNames(moduleFilter string) []string { return names } -// GetBusinessEventServiceNames returns qualified business event service names, optionally filtered by module. -func (e *Executor) GetBusinessEventServiceNames(moduleFilter string) []string { +// getBusinessEventServiceNamesAC returns qualified business event service names, optionally filtered by module. +func getBusinessEventServiceNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -336,12 +352,13 @@ func (e *Executor) GetBusinessEventServiceNames(moduleFilter string) []string { return names } -// GetJsonStructureNames returns qualified JSON structure names, optionally filtered by module. -func (e *Executor) GetJsonStructureNames(moduleFilter string) []string { +// getJsonStructureNamesAC returns qualified JSON structure names, optionally filtered by module. +func getJsonStructureNamesAC(ctx *ExecContext, moduleFilter string) []string { + e := ctx.executor if e.reader == nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -359,3 +376,82 @@ func (e *Executor) GetJsonStructureNames(moduleFilter string) []string { } return names } + +// ---------------------------------------------------------------------------- +// Exported Executor method wrappers (public API for external callers) +// ---------------------------------------------------------------------------- + +// GetModuleNames returns a list of all module names for autocomplete. +func (e *Executor) GetModuleNames() []string { + return getModuleNames(e.newExecContext(context.Background())) +} + +// GetMicroflowNames returns qualified microflow names, optionally filtered by module. +func (e *Executor) GetMicroflowNames(moduleFilter string) []string { + return getMicroflowNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetEntityNames returns qualified entity names, optionally filtered by module. +func (e *Executor) GetEntityNames(moduleFilter string) []string { + return getEntityNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetPageNames returns qualified page names, optionally filtered by module. +func (e *Executor) GetPageNames(moduleFilter string) []string { + return getPageNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetSnippetNames returns qualified snippet names, optionally filtered by module. +func (e *Executor) GetSnippetNames(moduleFilter string) []string { + return getSnippetNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetAssociationNames returns qualified association names, optionally filtered by module. +func (e *Executor) GetAssociationNames(moduleFilter string) []string { + return getAssociationNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetEnumerationNames returns qualified enumeration names, optionally filtered by module. +func (e *Executor) GetEnumerationNames(moduleFilter string) []string { + return getEnumerationNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetLayoutNames returns qualified layout names, optionally filtered by module. +func (e *Executor) GetLayoutNames(moduleFilter string) []string { + return getLayoutNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetJavaActionNames returns qualified Java action names, optionally filtered by module. +func (e *Executor) GetJavaActionNames(moduleFilter string) []string { + return getJavaActionNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetODataClientNames returns qualified consumed OData service names, optionally filtered by module. +func (e *Executor) GetODataClientNames(moduleFilter string) []string { + return getODataClientNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetODataServiceNames returns qualified published OData service names, optionally filtered by module. +func (e *Executor) GetODataServiceNames(moduleFilter string) []string { + return getODataServiceNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetRestClientNames returns qualified consumed REST service names, optionally filtered by module. +func (e *Executor) GetRestClientNames(moduleFilter string) []string { + return getRestClientNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetDatabaseConnectionNames returns qualified database connection names, optionally filtered by module. +func (e *Executor) GetDatabaseConnectionNames(moduleFilter string) []string { + return getDatabaseConnectionNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetBusinessEventServiceNames returns qualified business event service names, optionally filtered by module. +func (e *Executor) GetBusinessEventServiceNames(moduleFilter string) []string { + return getBusinessEventServiceNamesAC(e.newExecContext(context.Background()), moduleFilter) +} + +// GetJsonStructureNames returns qualified JSON structure names, optionally filtered by module. +func (e *Executor) GetJsonStructureNames(moduleFilter string) []string { + return getJsonStructureNamesAC(e.newExecContext(context.Background()), moduleFilter) +} diff --git a/mdl/executor/cmd_agenteditor_agents.go b/mdl/executor/cmd_agenteditor_agents.go index d8f119a0..0fa4362c 100644 --- a/mdl/executor/cmd_agenteditor_agents.go +++ b/mdl/executor/cmd_agenteditor_agents.go @@ -29,7 +29,7 @@ func showAgentEditorAgents(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list agents", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -60,7 +60,7 @@ func showAgentEditorAgents(ctx *ExecContext, moduleName string) error { } result.Summary = fmt.Sprintf("(%d agent(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // describeAgentEditorAgent handles DESCRIBE AGENT Module.Name. Emits a @@ -76,7 +76,7 @@ func describeAgentEditorAgent(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("agent", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -238,7 +238,7 @@ func findAgentEditorAgent(ctx *ExecContext, moduleName, agentName string) *agent if err != nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } diff --git a/mdl/executor/cmd_agenteditor_kbs.go b/mdl/executor/cmd_agenteditor_kbs.go index cea24d97..8009613d 100644 --- a/mdl/executor/cmd_agenteditor_kbs.go +++ b/mdl/executor/cmd_agenteditor_kbs.go @@ -29,7 +29,7 @@ func showAgentEditorKnowledgeBases(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list knowledge bases", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -59,7 +59,7 @@ func showAgentEditorKnowledgeBases(ctx *ExecContext, moduleName string) error { } result.Summary = fmt.Sprintf("(%d knowledge base(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // describeAgentEditorKnowledgeBase handles DESCRIBE KNOWLEDGE BASE Module.Name. @@ -74,7 +74,7 @@ func describeAgentEditorKnowledgeBase(ctx *ExecContext, name ast.QualifiedName) return mdlerrors.NewNotFound("knowledge base", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -134,7 +134,7 @@ func findAgentEditorKnowledgeBase(ctx *ExecContext, moduleName, kbName string) * if err != nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } diff --git a/mdl/executor/cmd_agenteditor_mcpservices.go b/mdl/executor/cmd_agenteditor_mcpservices.go index 416cc4ec..907b758e 100644 --- a/mdl/executor/cmd_agenteditor_mcpservices.go +++ b/mdl/executor/cmd_agenteditor_mcpservices.go @@ -29,7 +29,7 @@ func showAgentEditorConsumedMCPServices(ctx *ExecContext, moduleName string) err return mdlerrors.NewBackend("list consumed MCP services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -55,7 +55,7 @@ func showAgentEditorConsumedMCPServices(ctx *ExecContext, moduleName string) err } result.Summary = fmt.Sprintf("(%d consumed MCP service(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // describeAgentEditorConsumedMCPService handles DESCRIBE CONSUMED MCP SERVICE Module.Name. @@ -70,7 +70,7 @@ func describeAgentEditorConsumedMCPService(ctx *ExecContext, name ast.QualifiedN return mdlerrors.NewNotFound("consumed MCP service", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -118,7 +118,7 @@ func findAgentEditorConsumedMCPService(ctx *ExecContext, moduleName, svcName str if err != nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index 9cbba94d..bdf1d1d5 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -29,7 +29,7 @@ func showAgentEditorModels(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -61,7 +61,7 @@ func showAgentEditorModels(ctx *ExecContext, moduleName string) error { } result.Summary = fmt.Sprintf("(%d model(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // describeAgentEditorModel handles DESCRIBE MODEL Module.Name. @@ -77,7 +77,7 @@ func describeAgentEditorModel(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("model", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -140,7 +140,7 @@ func findAgentEditorModel(ctx *ExecContext, moduleName, modelName string) *agent if err != nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } diff --git a/mdl/executor/cmd_alter_page.go b/mdl/executor/cmd_alter_page.go index c4658f1a..35d2fbdc 100644 --- a/mdl/executor/cmd_alter_page.go +++ b/mdl/executor/cmd_alter_page.go @@ -25,7 +25,7 @@ func execAlterPage(ctx *ExecContext, s *ast.AlterPageStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_alter_workflow.go b/mdl/executor/cmd_alter_workflow.go index 97eee0dd..a1e79a99 100644 --- a/mdl/executor/cmd_alter_workflow.go +++ b/mdl/executor/cmd_alter_workflow.go @@ -37,7 +37,7 @@ func execAlterWorkflow(ctx *ExecContext, s *ast.AlterWorkflowStmt) error { return err } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -142,7 +142,7 @@ func execAlterWorkflow(ctx *ExecContext, s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("save modified workflow", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Altered workflow %s.%s\n", s.Name.Module, s.Name.Name) return nil } diff --git a/mdl/executor/cmd_associations.go b/mdl/executor/cmd_associations.go index 3336ea2d..eb6119c9 100644 --- a/mdl/executor/cmd_associations.go +++ b/mdl/executor/cmd_associations.go @@ -23,7 +23,7 @@ func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -38,7 +38,7 @@ func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error if parentModule == "" { parentModule = s.Name.Module } - parentEntity, err := e.findEntity(parentModule, s.Parent.Name) + parentEntity, err := findEntity(ctx, parentModule, s.Parent.Name) if err != nil { return mdlerrors.NewNotFound("parent entity", s.Parent.String()) } @@ -48,7 +48,7 @@ func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error if childModule == "" { childModule = s.Name.Module } - childEntity, err := e.findEntity(childModule, s.Child.Name) + childEntity, err := findEntity(ctx, childModule, s.Child.Name) if err != nil { return mdlerrors.NewNotFound("child entity", s.Child.String()) } @@ -128,8 +128,8 @@ func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error } // Invalidate hierarchy cache so the new association's container is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) // Reconcile MemberAccesses immediately — existing access rules on entities // in this DM need MemberAccess entries for the new association (CE0066). @@ -151,7 +151,7 @@ func execAlterAssociation(ctx *ExecContext, s *ast.AlterAssociationStmt) error { return mdlerrors.NewNotConnected() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -218,7 +218,7 @@ func execDropAssociation(ctx *ExecContext, s *ast.DropAssociationStmt) error { } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -334,7 +334,7 @@ func showAssociations(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.parent, r.child, r.assocType, r.owner, r.storage}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showAssociation handles SHOW ASSOCIATION command. @@ -344,7 +344,7 @@ func showAssociation(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewValidation("association name required") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -380,7 +380,7 @@ func showAssociation(ctx *ExecContext, name *ast.QualifiedName) error { // describeAssociation handles DESCRIBE ASSOCIATION command. func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -396,7 +396,7 @@ func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { if err != nil { return mdlerrors.NewBackend("list domain models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_businessevents.go b/mdl/executor/cmd_businessevents.go index 35f041c3..68b4bbb6 100644 --- a/mdl/executor/cmd_businessevents.go +++ b/mdl/executor/cmd_businessevents.go @@ -26,7 +26,7 @@ func showBusinessEventServices(ctx *ExecContext, inModule string) error { return mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -86,7 +86,7 @@ func showBusinessEventServices(ctx *ExecContext, inModule string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.name, r.msgCount, r.publishCount, r.subscribeCount}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showBusinessEventClients displays a table of all business event client documents. @@ -108,7 +108,7 @@ func showBusinessEvents(ctx *ExecContext, inModule string) error { return mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -171,7 +171,7 @@ func showBusinessEvents(ctx *ExecContext, inModule string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.service, r.message, r.operation, r.entity, r.attrs}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeBusinessEventService outputs the full MDL description of a business event service. @@ -188,7 +188,7 @@ func describeBusinessEventService(ctx *ExecContext, name ast.QualifiedName) erro } // Use hierarchy to resolve container IDs to module names - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -275,14 +275,14 @@ func createBusinessEventService(ctx *ExecContext, stmt *ast.CreateBusinessEventS } moduleName := stmt.Name.Module - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return mdlerrors.NewNotFound("module", moduleName) } // Check for existing service with same name (if not CREATE OR REPLACE) existingServices, _ := e.reader.ListBusinessEventServices() - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -305,7 +305,7 @@ func createBusinessEventService(ctx *ExecContext, stmt *ast.CreateBusinessEventS // Resolve folder if specified containerID := module.ID if stmt.Folder != "" { - folderID, err := e.resolveFolder(module.ID, stmt.Folder) + folderID, err := resolveFolder(ctx, module.ID, stmt.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder '%s'", stmt.Folder), err) } @@ -395,7 +395,7 @@ func dropBusinessEventService(ctx *ExecContext, stmt *ast.DropBusinessEventServi return mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_catalog.go b/mdl/executor/cmd_catalog.go index a2d8f44b..4682b530 100644 --- a/mdl/executor/cmd_catalog.go +++ b/mdl/executor/cmd_catalog.go @@ -4,6 +4,7 @@ package executor import ( "bytes" + "context" "encoding/json" "fmt" "os" @@ -18,16 +19,17 @@ import ( ) // execShowCatalogTables handles SHOW CATALOG TABLES. -func (e *Executor) execShowCatalogTables() error { +func execShowCatalogTables(ctx *ExecContext) error { + e := ctx.executor // Build catalog if not already built (fast mode by default) - if e.catalog == nil || !e.catalog.IsBuilt() { - if err := e.ensureCatalog(false); err != nil { + if ctx.Catalog == nil || !ctx.Catalog.IsBuilt() { + if err := ensureCatalog(ctx, false); err != nil { return err } } - tables := e.catalog.Tables() - fmt.Fprintf(e.output, "\nFound %d catalog table(s)\n", len(tables)) + tables := ctx.Catalog.Tables() + fmt.Fprintf(ctx.Output, "\nFound %d catalog table(s)\n", len(tables)) // Get row counts for each table type tableInfo struct { @@ -39,7 +41,7 @@ func (e *Executor) execShowCatalogTables() error { for _, t := range tables { // Get count for this table actualTable := strings.TrimPrefix(strings.ToLower(t), "catalog.") - result, err := e.catalog.Query(fmt.Sprintf("SELECT COUNT(*) FROM %s", actualTable)) + result, err := ctx.Catalog.Query(fmt.Sprintf("SELECT COUNT(*) FROM %s", actualTable)) count := 0 if err == nil && len(result.Rows) > 0 { if v, ok := result.Rows[0][0].(int64); ok { @@ -55,7 +57,8 @@ func (e *Executor) execShowCatalogTables() error { for _, info := range infos { tr.Rows = append(tr.Rows, []any{info.name, info.count}) } - return e.writeResult(tr) + _ = e // suppress unused + return writeResult(ctx, tr) } // fullOnlyTables are catalog tables only populated by REFRESH CATALOG FULL. @@ -90,7 +93,7 @@ func extractTableFromQuery(query string) string { // warnIfCatalogModeInsufficient checks if the query targets a table that requires // a higher catalog build mode than what's currently cached, and prints a warning. -func (e *Executor) warnIfCatalogModeInsufficient(query string) { +func warnIfCatalogModeInsufficient(ctx *ExecContext, query string) { table := extractTableFromQuery(query) if table == "" { return @@ -98,8 +101,8 @@ func (e *Executor) warnIfCatalogModeInsufficient(query string) { // Determine current build mode buildMode := "fast" - if e.catalog != nil { - if info, err := e.catalog.GetCacheInfo(); err == nil && info.BuildMode != "" { + if ctx.Catalog != nil { + if info, err := ctx.Catalog.GetCacheInfo(); err == nil && info.BuildMode != "" { buildMode = info.BuildMode } } @@ -108,17 +111,17 @@ func (e *Executor) warnIfCatalogModeInsufficient(query string) { currentRank := modeRank[buildMode] if sourceOnlyTables[table] && currentRank < modeRank["source"] { - fmt.Fprintf(e.output, "Warning: CATALOG.%s requires REFRESH CATALOG FULL SOURCE (current mode: %s)\n", strings.ToUpper(table), buildMode) + fmt.Fprintf(ctx.Output, "Warning: CATALOG.%s requires REFRESH CATALOG FULL SOURCE (current mode: %s)\n", strings.ToUpper(table), buildMode) } else if fullOnlyTables[table] && currentRank < modeRank["full"] { - fmt.Fprintf(e.output, "Warning: CATALOG.%s requires REFRESH CATALOG FULL (current mode: %s)\n", strings.ToUpper(table), buildMode) + fmt.Fprintf(ctx.Output, "Warning: CATALOG.%s requires REFRESH CATALOG FULL (current mode: %s)\n", strings.ToUpper(table), buildMode) } } // execCatalogQuery handles SELECT ... FROM CATALOG.xxx queries. -func (e *Executor) execCatalogQuery(query string) error { +func execCatalogQuery(ctx *ExecContext, query string) error { // Build catalog if not already built (fast mode by default) - if e.catalog == nil || !e.catalog.IsBuilt() { - if err := e.ensureCatalog(false); err != nil { + if ctx.Catalog == nil || !ctx.Catalog.IsBuilt() { + if err := ensureCatalog(ctx, false); err != nil { return err } } @@ -127,22 +130,22 @@ func (e *Executor) execCatalogQuery(query string) error { query = convertCatalogTableNames(query) // Warn if querying a table that requires a higher build mode - e.warnIfCatalogModeInsufficient(query) + warnIfCatalogModeInsufficient(ctx, query) // Execute query - result, err := e.catalog.Query(query) + result, err := ctx.Catalog.Query(query) if err != nil { return mdlerrors.NewBackend("execute catalog query", err) } // Output results - fmt.Fprintf(e.output, "Found %d result(s)\n", result.Count) + fmt.Fprintf(ctx.Output, "Found %d result(s)\n", result.Count) if result.Count == 0 { - fmt.Fprintln(e.output, "(no results)") + fmt.Fprintln(ctx.Output, "(no results)") return nil } - e.outputCatalogResults(result) + outputCatalogResults(ctx, result) return nil } @@ -158,10 +161,10 @@ func tableRequiredMode(table string) string { } // execDescribeCatalogTable handles DESCRIBE CATALOG.tablename. -func (e *Executor) execDescribeCatalogTable(stmt *ast.DescribeCatalogTableStmt) error { +func execDescribeCatalogTable(ctx *ExecContext, stmt *ast.DescribeCatalogTableStmt) error { // Build catalog if not already built (fast mode by default) - if e.catalog == nil || !e.catalog.IsBuilt() { - if err := e.ensureCatalog(false); err != nil { + if ctx.Catalog == nil || !ctx.Catalog.IsBuilt() { + if err := ensureCatalog(ctx, false); err != nil { return err } } @@ -169,14 +172,14 @@ func (e *Executor) execDescribeCatalogTable(stmt *ast.DescribeCatalogTableStmt) tableName := stmt.TableName // Query column info using PRAGMA - result, err := e.catalog.Query(fmt.Sprintf("PRAGMA table_info(%s)", tableName)) + result, err := ctx.Catalog.Query(fmt.Sprintf("PRAGMA table_info(%s)", tableName)) if err != nil || result.Count == 0 { return mdlerrors.NewNotFoundMsg("catalog table", strings.ToUpper(tableName), "unknown catalog table: CATALOG."+strings.ToUpper(tableName)) } // Print table header - fmt.Fprintf(e.output, "\nCATALOG.%s\n", strings.ToUpper(tableName)) - fmt.Fprintf(e.output, "Requires: %s\n\n", tableRequiredMode(tableName)) + fmt.Fprintf(ctx.Output, "\nCATALOG.%s\n", strings.ToUpper(tableName)) + fmt.Fprintf(ctx.Output, "Requires: %s\n\n", tableRequiredMode(tableName)) // PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk tr := &TableResult{ @@ -191,31 +194,34 @@ func (e *Executor) execDescribeCatalogTable(stmt *ast.DescribeCatalogTableStmt) } tr.Rows = append(tr.Rows, []any{name, typ, pkMark}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // ensureCatalog ensures a catalog is available, using cache if possible. -func (e *Executor) ensureCatalog(full bool) error { +func ensureCatalog(ctx *ExecContext, full bool) error { + e := ctx.executor requiredMode := "fast" if full { requiredMode = "full" } // Try to load from cache first - cachePath := e.getCachePath() + cachePath := getCachePath(ctx) if cachePath != "" { - valid, _ := e.isCacheValid(cachePath, requiredMode) + valid, _ := isCacheValid(ctx, cachePath, requiredMode) if valid { - return e.loadCachedCatalog(cachePath) + return loadCachedCatalog(ctx, cachePath) } } // Build fresh catalog - return e.buildCatalog(full) + _ = e // suppress unused + return buildCatalog(ctx, full) } // getCachePath returns the path to the catalog cache file for the current project. -func (e *Executor) getCachePath() string { +func getCachePath(ctx *ExecContext) string { + e := ctx.executor if e.mprPath == "" { return "" } @@ -225,7 +231,8 @@ func (e *Executor) getCachePath() string { } // getMprModTime returns the modification time of the current MPR file. -func (e *Executor) getMprModTime() time.Time { +func getMprModTime(ctx *ExecContext) time.Time { + e := ctx.executor if e.mprPath == "" { return time.Time{} } @@ -237,7 +244,8 @@ func (e *Executor) getMprModTime() time.Time { } // isCacheValid checks if the cached catalog is still valid. -func (e *Executor) isCacheValid(cachePath string, requiredMode string) (bool, string) { +func isCacheValid(ctx *ExecContext, cachePath string, requiredMode string) (bool, string) { + e := ctx.executor // Check if cache file exists if _, err := os.Stat(cachePath); os.IsNotExist(err) { return false, "cache file does not exist" @@ -261,7 +269,7 @@ func (e *Executor) isCacheValid(cachePath string, requiredMode string) (bool, st } // Check MPR modification time (compare Unix seconds to handle timezone/precision issues) - currentModTime := e.getMprModTime() + currentModTime := getMprModTime(ctx) if info.MprModTime.Unix() != currentModTime.Unix() { return false, "project file modified" } @@ -278,7 +286,8 @@ func (e *Executor) isCacheValid(cachePath string, requiredMode string) (bool, st } // loadCachedCatalog loads a catalog from the cache file. -func (e *Executor) loadCachedCatalog(cachePath string) error { +func loadCachedCatalog(ctx *ExecContext, cachePath string) error { + e := ctx.executor cat, err := catalog.NewFromFile(cachePath) if err != nil { return err @@ -290,12 +299,13 @@ func (e *Executor) loadCachedCatalog(cachePath string) error { return err } + ctx.Catalog = cat e.catalog = cat - if !e.quiet { + if !ctx.Quiet { age := time.Since(info.BuildTime) - fmt.Fprintf(e.output, "Loading cached catalog (built %s ago, %s mode)...\n", + fmt.Fprintf(ctx.Output, "Loading cached catalog (built %s ago, %s mode)...\n", formatDuration(age), info.BuildMode) - fmt.Fprintf(e.output, "✓ Catalog ready (from cache)\n") + fmt.Fprintf(ctx.Output, "✓ Catalog ready (from cache)\n") } return nil } @@ -315,19 +325,20 @@ func formatDuration(d time.Duration) string { } // buildCatalog builds the catalog from the project. -func (e *Executor) buildCatalog(full bool, source ...bool) error { +func buildCatalog(ctx *ExecContext, full bool, source ...bool) error { + e := ctx.executor isSource := len(source) > 0 && source[0] if isSource { full = true // source implies full } - if !e.quiet { + if !ctx.Quiet { if isSource { - fmt.Fprintln(e.output, "Building catalog (source mode - includes MDL source)...") + fmt.Fprintln(ctx.Output, "Building catalog (source mode - includes MDL source)...") } else if full { - fmt.Fprintln(e.output, "Building catalog (full mode - includes activities/widgets)...") + fmt.Fprintln(ctx.Output, "Building catalog (full mode - includes activities/widgets)...") } else { - fmt.Fprintln(e.output, "Building catalog (fast mode)...") + fmt.Fprintln(ctx.Output, "Building catalog (fast mode)...") } } start := time.Now() @@ -347,11 +358,13 @@ func (e *Executor) buildCatalog(full bool, source ...bool) error { builder.SetFullMode(full) if isSource { builder.SetSourceMode(true) - e.PreWarmCache() - builder.SetDescribeFunc(e.captureDescribeParallel) + preWarmCache(ctx) + builder.SetDescribeFunc(func(objectType string, qualifiedName string) (string, error) { + return captureDescribeParallel(ctx, objectType, qualifiedName) + }) } err = builder.Build(func(table string, count int) { - fmt.Fprintf(e.output, "✓ %s: %d\n", table, count) + fmt.Fprintf(ctx.Output, "✓ %s: %d\n", table, count) }) if err != nil { cat.Close() @@ -367,26 +380,27 @@ func (e *Executor) buildCatalog(full bool, source ...bool) error { } else if full { buildMode = "full" } - cat.SetCacheInfo(e.mprPath, e.getMprModTime(), version, buildMode, elapsed) + cat.SetCacheInfo(e.mprPath, getMprModTime(ctx), version, buildMode, elapsed) cat.SetBuilt(true) + ctx.Catalog = cat e.catalog = cat - if !e.quiet { - fmt.Fprintf(e.output, "✓ Catalog ready (%.1fs)\n", elapsed.Seconds()) + if !ctx.Quiet { + fmt.Fprintf(ctx.Output, "✓ Catalog ready (%.1fs)\n", elapsed.Seconds()) } // Save to cache file - cachePath := e.getCachePath() + cachePath := getCachePath(ctx) if cachePath != "" { cacheDir := filepath.Dir(cachePath) if err := os.MkdirAll(cacheDir, 0755); err == nil { // Remove existing cache file first os.Remove(cachePath) if err := cat.SaveToFile(cachePath); err != nil { - fmt.Fprintf(e.output, "Warning: failed to save catalog cache: %v\n", err) + fmt.Fprintf(ctx.Output, "Warning: failed to save catalog cache: %v\n", err) } else { - fmt.Fprintf(e.output, "✓ Catalog cached to %s\n", cachePath) + fmt.Fprintf(ctx.Output, "✓ Catalog cached to %s\n", cachePath) } } } @@ -395,7 +409,8 @@ func (e *Executor) buildCatalog(full bool, source ...bool) error { } // execRefreshCatalogStmt handles REFRESH CATALOG [FULL] [SOURCE] [FORCE] [BACKGROUND] command. -func (e *Executor) execRefreshCatalogStmt(stmt *ast.RefreshCatalogStmt) error { +func execRefreshCatalogStmt(ctx *ExecContext, stmt *ast.RefreshCatalogStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -410,22 +425,23 @@ func (e *Executor) execRefreshCatalogStmt(stmt *ast.RefreshCatalogStmt) error { // Check cache unless FORCE is specified if !stmt.Force { - cachePath := e.getCachePath() + cachePath := getCachePath(ctx) if cachePath != "" { - valid, reason := e.isCacheValid(cachePath, requiredMode) + valid, reason := isCacheValid(ctx, cachePath, requiredMode) if valid { // Close existing catalog if any - if e.catalog != nil { - e.catalog.Close() + if ctx.Catalog != nil { + ctx.Catalog.Close() + ctx.Catalog = nil e.catalog = nil } - return e.loadCachedCatalog(cachePath) + return loadCachedCatalog(ctx, cachePath) } if reason != "cache file does not exist" { - fmt.Fprintf(e.output, "Cache invalid: %s\n", reason) + fmt.Fprintf(ctx.Output, "Cache invalid: %s\n", reason) // If project file was modified, reconnect to get fresh database connection if reason == "project file modified" { - if err := e.reconnect(); err != nil { + if err := reconnect(ctx); err != nil { return mdlerrors.NewBackend("reconnect after project modification", err) } } @@ -434,87 +450,88 @@ func (e *Executor) execRefreshCatalogStmt(stmt *ast.RefreshCatalogStmt) error { } // Close existing catalog if any - if e.catalog != nil { - e.catalog.Close() + if ctx.Catalog != nil { + ctx.Catalog.Close() + ctx.Catalog = nil e.catalog = nil } // Handle background mode if stmt.Background { go func() { - if err := e.buildCatalog(stmt.Full, stmt.Source); err != nil { - fmt.Fprintf(e.output, "Background catalog build failed: %v\n", err) + if err := buildCatalog(ctx, stmt.Full, stmt.Source); err != nil { + fmt.Fprintf(ctx.Output, "Background catalog build failed: %v\n", err) } }() - fmt.Fprintln(e.output, "Catalog build started in background...") + fmt.Fprintln(ctx.Output, "Catalog build started in background...") return nil } // Rebuild the catalog - return e.buildCatalog(stmt.Full, stmt.Source) + return buildCatalog(ctx, stmt.Full, stmt.Source) } // execRefreshCatalog handles REFRESH CATALOG [FULL] command (legacy signature). -func (e *Executor) execRefreshCatalog(full bool) error { - return e.execRefreshCatalogStmt(&ast.RefreshCatalogStmt{Full: full, Source: false}) +func execRefreshCatalog(ctx *ExecContext, full bool) error { + return execRefreshCatalogStmt(ctx, &ast.RefreshCatalogStmt{Full: full, Source: false}) } // execShowCatalogStatus handles SHOW CATALOG STATUS command. -func (e *Executor) execShowCatalogStatus() error { - cachePath := e.getCachePath() +func execShowCatalogStatus(ctx *ExecContext) error { + cachePath := getCachePath(ctx) if cachePath == "" { - fmt.Fprintln(e.output, "No project connected") + fmt.Fprintln(ctx.Output, "No project connected") return nil } - fmt.Fprintf(e.output, "\nCatalog Cache Status\n") - fmt.Fprintf(e.output, "────────────────────\n") - fmt.Fprintf(e.output, "Cache path: %s\n", cachePath) + fmt.Fprintf(ctx.Output, "\nCatalog Cache Status\n") + fmt.Fprintf(ctx.Output, "────────────────────\n") + fmt.Fprintf(ctx.Output, "Cache path: %s\n", cachePath) // Check if cache exists if _, err := os.Stat(cachePath); os.IsNotExist(err) { - fmt.Fprintln(e.output, "Status: No cache file") + fmt.Fprintln(ctx.Output, "Status: No cache file") return nil } // Load cache info cat, err := catalog.NewFromFile(cachePath) if err != nil { - fmt.Fprintf(e.output, "Status: Cache file corrupt (%v)\n", err) + fmt.Fprintf(ctx.Output, "Status: Cache file corrupt (%v)\n", err) return nil } defer cat.Close() info, err := cat.GetCacheInfo() if err != nil { - fmt.Fprintf(e.output, "Status: Cannot read cache info (%v)\n", err) + fmt.Fprintf(ctx.Output, "Status: Cannot read cache info (%v)\n", err) return nil } // Display cache info - fmt.Fprintf(e.output, "Build mode: %s\n", info.BuildMode) - fmt.Fprintf(e.output, "Build time: %s\n", info.BuildTime.Format("2006-01-02 15:04:05")) - fmt.Fprintf(e.output, "Build duration: %s\n", info.BuildDuration) - fmt.Fprintf(e.output, "Mendix version: %s\n", info.MendixVersion) + fmt.Fprintf(ctx.Output, "Build mode: %s\n", info.BuildMode) + fmt.Fprintf(ctx.Output, "Build time: %s\n", info.BuildTime.Format("2006-01-02 15:04:05")) + fmt.Fprintf(ctx.Output, "Build duration: %s\n", info.BuildDuration) + fmt.Fprintf(ctx.Output, "Mendix version: %s\n", info.MendixVersion) // Check validity - valid, reason := e.isCacheValid(cachePath, "fast") + valid, reason := isCacheValid(ctx, cachePath, "fast") if valid { - fmt.Fprintln(e.output, "Status: ✓ Valid") + fmt.Fprintln(ctx.Output, "Status: ✓ Valid") } else { - fmt.Fprintf(e.output, "Status: ✗ Invalid (%s)\n", reason) + fmt.Fprintf(ctx.Output, "Status: ✗ Invalid (%s)\n", reason) } // Also check full mode validity if info.BuildMode == "full" || info.BuildMode == "source" { - fmt.Fprintln(e.output, "Full mode: ✓ Available") + fmt.Fprintln(ctx.Output, "Full mode: ✓ Available") } else { - fmt.Fprintln(e.output, "Full mode: ✗ Not cached (use REFRESH CATALOG FULL)") + fmt.Fprintln(ctx.Output, "Full mode: ✗ Not cached (use REFRESH CATALOG FULL)") } if info.BuildMode == "source" { - fmt.Fprintln(e.output, "Source mode: ✓ Available") + fmt.Fprintln(ctx.Output, "Source mode: ✓ Available") } else { - fmt.Fprintln(e.output, "Source mode: ✗ Not cached (use REFRESH CATALOG SOURCE)") + fmt.Fprintln(ctx.Output, "Source mode: ✗ Not cached (use REFRESH CATALOG SOURCE)") } return nil @@ -592,7 +609,7 @@ func replaceIgnoreCase(s, old, new string) string { } // outputCatalogResults outputs query results in table or JSON format. -func (e *Executor) outputCatalogResults(result *catalog.QueryResult) { +func outputCatalogResults(ctx *ExecContext, result *catalog.QueryResult) { tr := &TableResult{ Columns: result.Columns, } @@ -603,7 +620,7 @@ func (e *Executor) outputCatalogResults(result *catalog.QueryResult) { } tr.Rows = append(tr.Rows, outRow) } - _ = e.writeResult(tr) + _ = writeResult(ctx, tr) } // formatValue formats a value for display. @@ -623,9 +640,9 @@ func formatValue(val any) string { } // captureDescribe generates MDL source for a given object type and qualified name. -// It temporarily redirects e.output to capture the describe output. +// It temporarily redirects ctx.Output to capture the describe output. // NOT safe for concurrent use — use captureDescribeParallel instead. -func (e *Executor) captureDescribe(objectType string, qualifiedName string) (string, error) { +func captureDescribe(ctx *ExecContext, objectType string, qualifiedName string) (string, error) { // Parse qualified name into ast.QualifiedName parts := strings.SplitN(qualifiedName, ".", 2) if len(parts) != 2 { @@ -634,25 +651,25 @@ func (e *Executor) captureDescribe(objectType string, qualifiedName string) (str qn := ast.QualifiedName{Module: parts[0], Name: parts[1]} // Save original output and redirect to buffer - origOutput := e.output + origOutput := ctx.Output var buf bytes.Buffer - e.output = &buf - defer func() { e.output = origOutput }() + ctx.Output = &buf + defer func() { ctx.Output = origOutput }() var err error switch strings.ToUpper(objectType) { case "ENTITY": - err = e.describeEntity(qn) + err = describeEntity(ctx, qn) case "MICROFLOW", "NANOFLOW": - err = e.describeMicroflow(qn) + err = describeMicroflow(ctx, qn) case "PAGE": - err = e.describePage(qn) + err = describePage(ctx, qn) case "SNIPPET": - err = e.describeSnippet(qn) + err = describeSnippet(ctx, qn) case "ENUMERATION": - err = e.describeEnumeration(qn) + err = describeEnumeration(ctx, qn) case "WORKFLOW": - err = e.describeWorkflow(qn) + err = describeWorkflow(ctx, qn) default: return "", mdlerrors.NewUnsupported("object type for describe: " + objectType) } @@ -664,10 +681,10 @@ func (e *Executor) captureDescribe(objectType string, qualifiedName string) (str } // captureDescribeParallel is a goroutine-safe version of captureDescribe. -// It creates a lightweight executor clone per call with its own output buffer, -// sharing the reader and pre-warmed cache. Call PreWarmCache() before using +// It creates a lightweight ExecContext clone per call with its own output buffer, +// sharing the reader and pre-warmed cache. Call preWarmCache() before using // this from multiple goroutines. -func (e *Executor) captureDescribeParallel(objectType string, qualifiedName string) (string, error) { +func captureDescribeParallel(ctx *ExecContext, objectType string, qualifiedName string) (string, error) { parts := strings.SplitN(qualifiedName, ".", 2) if len(parts) != 2 { return "", mdlerrors.NewValidationf("invalid qualified name: %s", qualifiedName) @@ -677,25 +694,30 @@ func (e *Executor) captureDescribeParallel(objectType string, qualifiedName stri // Create a goroutine-local executor: shared reader + cache, own output buffer var buf bytes.Buffer local := &Executor{ - reader: e.reader, + reader: ctx.executor.reader, output: &buf, - cache: e.cache, + cache: ctx.Cache, + } + localCtx := &ExecContext{ + Output: &buf, + Cache: ctx.Cache, + executor: local, } var err error switch strings.ToUpper(objectType) { case "ENTITY": - err = local.describeEntity(qn) + err = describeEntity(localCtx, qn) case "MICROFLOW", "NANOFLOW": - err = local.describeMicroflow(qn) + err = describeMicroflow(localCtx, qn) case "PAGE": - err = local.describePage(qn) + err = describePage(localCtx, qn) case "SNIPPET": - err = local.describeSnippet(qn) + err = describeSnippet(localCtx, qn) case "ENUMERATION": - err = local.describeEnumeration(qn) + err = describeEnumeration(localCtx, qn) case "WORKFLOW": - err = local.describeWorkflow(qn) + err = describeWorkflow(localCtx, qn) default: return "", mdlerrors.NewUnsupported("object type for describe: " + objectType) } @@ -706,49 +728,56 @@ func (e *Executor) captureDescribeParallel(objectType string, qualifiedName stri return buf.String(), nil } -// PreWarmCache ensures all caches are populated before parallel operations. +// preWarmCache ensures all caches are populated before parallel operations. // Must be called from the main goroutine before using captureDescribeParallel. // This avoids O(n²) re-parsing in describe functions by building name lookup // maps once and sharing them across all goroutines. -func (e *Executor) PreWarmCache() { - h, _ := e.getHierarchy() - if h == nil || e.cache == nil { +func preWarmCache(ctx *ExecContext) { + e := ctx.executor + h, _ := getHierarchy(ctx) + if h == nil || ctx.Cache == nil { return } // Build entity name lookup - e.cache.entityNames = make(map[model.ID]string) + ctx.Cache.entityNames = make(map[model.ID]string) dms, _ := e.reader.ListDomainModels() for _, dm := range dms { modName := h.GetModuleName(dm.ContainerID) for _, ent := range dm.Entities { - e.cache.entityNames[ent.ID] = modName + "." + ent.Name + ctx.Cache.entityNames[ent.ID] = modName + "." + ent.Name } } // Build microflow name lookup - e.cache.microflowNames = make(map[model.ID]string) + ctx.Cache.microflowNames = make(map[model.ID]string) mfs, _ := e.reader.ListMicroflows() for _, mf := range mfs { - e.cache.microflowNames[mf.ID] = h.GetQualifiedName(mf.ContainerID, mf.Name) + ctx.Cache.microflowNames[mf.ID] = h.GetQualifiedName(mf.ContainerID, mf.Name) } // Build page name lookup - e.cache.pageNames = make(map[model.ID]string) + ctx.Cache.pageNames = make(map[model.ID]string) pgs, _ := e.reader.ListPages() for _, pg := range pgs { - e.cache.pageNames[pg.ID] = h.GetQualifiedName(pg.ContainerID, pg.Name) + ctx.Cache.pageNames[pg.ID] = h.GetQualifiedName(pg.ContainerID, pg.Name) } } +// PreWarmCache ensures all caches are populated before parallel operations. +func (e *Executor) PreWarmCache() { + preWarmCache(e.newExecContext(context.Background())) +} + // execSearch handles SEARCH 'query' command. -func (e *Executor) execSearch(stmt *ast.SearchStmt) error { +func execSearch(ctx *ExecContext, stmt *ast.SearchStmt) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } // Ensure catalog is built (at least full mode for strings table) - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -759,29 +788,29 @@ func (e *Executor) execSearch(stmt *ast.SearchStmt) error { stringsQuery := fmt.Sprintf( "SELECT QualifiedName, ObjectType, snippet(strings, 2, '>>>', '<<<', '...', 32) AS Match, StringContext, ModuleName FROM strings WHERE strings MATCH '%s' LIMIT 50", escapeFTSQuery(query)) - strResult, err := e.catalog.Query(stringsQuery) + strResult, err := ctx.Catalog.Query(stringsQuery) if err == nil && strResult.Count > 0 { found = true - fmt.Fprintf(e.output, "\nString Matches (%d)\n", strResult.Count) - fmt.Fprintln(e.output, strings.Repeat("─", 40)) - e.outputCatalogResults(strResult) + fmt.Fprintf(ctx.Output, "\nString Matches (%d)\n", strResult.Count) + fmt.Fprintln(ctx.Output, strings.Repeat("─", 40)) + outputCatalogResults(ctx, strResult) } // Search source table (if available) sourceQuery := fmt.Sprintf( "SELECT QualifiedName, ObjectType, snippet(source, 2, '>>>', '<<<', '...', 48) AS Match, ModuleName FROM source WHERE source MATCH '%s' LIMIT 50", escapeFTSQuery(query)) - srcResult, err := e.catalog.Query(sourceQuery) + srcResult, err := ctx.Catalog.Query(sourceQuery) if err == nil && srcResult.Count > 0 { found = true - fmt.Fprintf(e.output, "\nSource Matches (%d)\n", srcResult.Count) - fmt.Fprintln(e.output, strings.Repeat("─", 40)) - e.outputCatalogResults(srcResult) + fmt.Fprintf(ctx.Output, "\nSource Matches (%d)\n", srcResult.Count) + fmt.Fprintln(ctx.Output, strings.Repeat("─", 40)) + outputCatalogResults(ctx, srcResult) } if !found { - fmt.Fprintln(e.output, "No matches found.") - fmt.Fprintln(e.output, "Tip: Use REFRESH CATALOG SOURCE to enable source-level search.") + fmt.Fprintln(ctx.Output, "No matches found.") + fmt.Fprintln(ctx.Output, "Tip: Use REFRESH CATALOG SOURCE to enable source-level search.") } return nil @@ -802,15 +831,16 @@ func escapeFTSQuery(q string) string { return q } -// Search performs a full-text search with the specified output format. +// search performs a full-text search with the specified output format. // Format can be: "table" (default), "names" (just qualified names), "json" -func (e *Executor) Search(query, format string) error { +func search(ctx *ExecContext, query, format string) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } // Ensure catalog is built (at least full mode for strings table) - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -830,7 +860,7 @@ func (e *Executor) Search(query, format string) error { stringsQuery := fmt.Sprintf( "SELECT QualifiedName, ObjectType, snippet(strings, 2, '>>>', '<<<', '...', 32) AS Match, StringContext, ModuleName FROM strings WHERE strings MATCH '%s' LIMIT 50", escapeFTSQuery(query)) - strResult, err := e.catalog.Query(stringsQuery) + strResult, err := ctx.Catalog.Query(stringsQuery) if err == nil && strResult.Count > 0 { for _, row := range strResult.Rows { r := searchResult{ @@ -849,7 +879,7 @@ func (e *Executor) Search(query, format string) error { sourceQuery := fmt.Sprintf( "SELECT QualifiedName, ObjectType, snippet(source, 2, '>>>', '<<<', '...', 48) AS Match, ModuleName FROM source WHERE source MATCH '%s' LIMIT 50", escapeFTSQuery(query)) - srcResult, err := e.catalog.Query(sourceQuery) + srcResult, err := ctx.Catalog.Query(sourceQuery) if err == nil && srcResult.Count > 0 { for _, row := range srcResult.Rows { r := searchResult{ @@ -865,10 +895,10 @@ func (e *Executor) Search(query, format string) error { if len(allResults) == 0 { if format != "json" { - fmt.Fprintln(e.output, "No matches found.") - fmt.Fprintln(e.output, "Tip: Use REFRESH CATALOG SOURCE to enable source-level search.") + fmt.Fprintln(ctx.Output, "No matches found.") + fmt.Fprintln(ctx.Output, "Tip: Use REFRESH CATALOG SOURCE to enable source-level search.") } else { - fmt.Fprintln(e.output, "[]") + fmt.Fprintln(ctx.Output, "[]") } return nil } @@ -880,7 +910,7 @@ func (e *Executor) Search(query, format string) error { key := r.ObjectType + ":" + r.QualifiedName if !seen[key] { seen[key] = true - fmt.Fprintf(e.output, "%s\t%s\n", strings.ToLower(r.ObjectType), r.QualifiedName) + fmt.Fprintf(ctx.Output, "%s\t%s\n", strings.ToLower(r.ObjectType), r.QualifiedName) } } case "json": @@ -888,10 +918,15 @@ func (e *Executor) Search(query, format string) error { if err != nil { return err } - fmt.Fprintln(e.output, string(jsonBytes)) + fmt.Fprintln(ctx.Output, string(jsonBytes)) default: // "table" - return e.execSearch(&ast.SearchStmt{Query: query}) + return execSearch(ctx, &ast.SearchStmt{Query: query}) } return nil } + +// Search performs a full-text search with the specified output format. +func (e *Executor) Search(query, format string) error { + return search(e.newExecContext(context.Background()), query, format) +} diff --git a/mdl/executor/cmd_constants.go b/mdl/executor/cmd_constants.go index dd41ca61..269824bb 100644 --- a/mdl/executor/cmd_constants.go +++ b/mdl/executor/cmd_constants.go @@ -23,7 +23,7 @@ func showConstants(ctx *ExecContext, moduleName string) error { } // Use hierarchy for proper module resolution (handles constants inside folders) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -79,7 +79,7 @@ func showConstants(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath, r.typeStr, r.defaultStr, r.exposed}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showConstants is an Executor method wrapper for callers not yet migrated. @@ -97,7 +97,7 @@ func describeConstant(ctx *ExecContext, name ast.QualifiedName) error { } // Use hierarchy for proper module resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -121,8 +121,6 @@ func (e *Executor) describeConstant(name ast.QualifiedName) error { // outputConstantMDL outputs a constant definition in MDL format. func outputConstantMDL(ctx *ExecContext, c *model.Constant, moduleName string) error { - e := ctx.executor - // Format default value based on type defaultValueStr := formatDefaultValue(c.Type, c.DefaultValue) @@ -131,7 +129,7 @@ func outputConstantMDL(ctx *ExecContext, c *model.Constant, moduleName string) e fmt.Fprintf(ctx.Output, " DEFAULT %s", defaultValueStr) // Add folder if present - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) if h != nil { if folderPath := h.BuildFolderPath(c.ContainerID); folderPath != "" { fmt.Fprintf(ctx.Output, "\n FOLDER '%s'", folderPath) @@ -282,7 +280,7 @@ func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { } // Find or auto-create module - module, err := e.findOrCreateModule(stmt.Name.Module) + module, err := findOrCreateModule(ctx, stmt.Name.Module) if err != nil { return err } @@ -299,7 +297,7 @@ func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { // Check if constant already exists in this module existingConstants, err := e.reader.ListConstants() if err == nil { - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) for _, c := range existingConstants { modID := h.FindModuleID(c.ContainerID) modName := h.GetModuleName(modID) @@ -317,7 +315,7 @@ func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { if err := e.writer.UpdateConstant(c); err != nil { return mdlerrors.NewBackend("update constant", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Modified constant: %s.%s\n", modName, c.Name) return nil } @@ -334,7 +332,7 @@ func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { containerID := module.ID if stmt.Folder != "" { - folderID, err := e.resolveFolder(module.ID, stmt.Folder) + folderID, err := resolveFolder(ctx, module.ID, stmt.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder %s", stmt.Folder), err) } @@ -353,7 +351,7 @@ func createConstant(ctx *ExecContext, stmt *ast.CreateConstantStmt) error { if err := e.writer.CreateConstant(constant); err != nil { return mdlerrors.NewBackend("create constant", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created constant: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } @@ -373,7 +371,7 @@ func showConstantValues(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("read project settings", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -452,7 +450,7 @@ func showConstantValues(ctx *ExecContext, moduleName string) error { } result.Rows = append(result.Rows, []any{r.constant, r.configuration, val}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showConstantValues is an Executor method wrapper for callers not yet migrated. @@ -474,7 +472,7 @@ func dropConstant(ctx *ExecContext, stmt *ast.DropConstantStmt) error { } // Use hierarchy for proper module resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -487,7 +485,7 @@ func dropConstant(ctx *ExecContext, stmt *ast.DropConstantStmt) error { if err := e.writer.DeleteConstant(c.ID); err != nil { return mdlerrors.NewBackend("drop constant", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped constant: %s.%s\n", modName, c.Name) return nil } diff --git a/mdl/executor/cmd_context.go b/mdl/executor/cmd_context.go index 6080dd3e..000dd27b 100644 --- a/mdl/executor/cmd_context.go +++ b/mdl/executor/cmd_context.go @@ -14,13 +14,12 @@ import ( // execShowContext handles SHOW CONTEXT OF [DEPTH n] command. // It assembles relevant context information for LLM consumption. func execShowContext(ctx *ExecContext, s *ast.ShowStmt) error { - e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("SHOW CONTEXT requires a qualified name") } // Ensure catalog is built with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return mdlerrors.NewBackend("build catalog", err) } @@ -376,7 +375,6 @@ func assembleEnumerationContext(ctx *ExecContext, out *strings.Builder, name str // assembleSnippetContext assembles context for a snippet. func assembleSnippetContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { - e := ctx.executor out.WriteString("### Snippet Definition\n\n") result, err := ctx.Catalog.Query(fmt.Sprintf( "SELECT Name, ParameterCount, WidgetCount FROM snippets WHERE QualifiedName = '%s'", name)) @@ -396,10 +394,10 @@ func assembleSnippetContext(ctx *ExecContext, out *strings.Builder, name string, ObjectType: ast.DescribeSnippet, Name: ast.QualifiedName{Module: parts[0], Name: parts[1]}, } - savedOutput := e.output - e.output = out - e.execDescribe(descStmt) - e.output = savedOutput + savedOutput := ctx.Output + ctx.Output = out + execDescribe(ctx, descStmt) + ctx.Output = savedOutput } out.WriteString("```\n\n") @@ -420,7 +418,6 @@ func assembleSnippetContext(ctx *ExecContext, out *strings.Builder, name string, // assembleJavaActionContext assembles context for a java action. func assembleJavaActionContext(ctx *ExecContext, out *strings.Builder, name string) { - e := ctx.executor out.WriteString("### Java Action Definition\n\n```sql\n") parts := strings.SplitN(name, ".", 2) if len(parts) == 2 { @@ -428,10 +425,10 @@ func assembleJavaActionContext(ctx *ExecContext, out *strings.Builder, name stri ObjectType: ast.DescribeJavaAction, Name: ast.QualifiedName{Module: parts[0], Name: parts[1]}, } - savedOutput := e.output - e.output = out - e.execDescribe(descStmt) - e.output = savedOutput + savedOutput := ctx.Output + ctx.Output = out + execDescribe(ctx, descStmt) + ctx.Output = savedOutput } out.WriteString("```\n\n") @@ -481,7 +478,6 @@ func assembleODataClientContext(ctx *ExecContext, out *strings.Builder, name str // assembleWorkflowContext assembles context for a workflow. func assembleWorkflowContext(ctx *ExecContext, out *strings.Builder, name string, depth int) { - e := ctx.executor // Get workflow basic info out.WriteString("### Workflow Definition\n\n") result, err := ctx.Catalog.Query(fmt.Sprintf( @@ -510,10 +506,10 @@ func assembleWorkflowContext(ctx *ExecContext, out *strings.Builder, name string ObjectType: ast.DescribeWorkflow, Name: ast.QualifiedName{Module: parts[0], Name: parts[1]}, } - savedOutput := e.output - e.output = out - e.execDescribe(descStmt) - e.output = savedOutput + savedOutput := ctx.Output + ctx.Output = out + execDescribe(ctx, descStmt) + ctx.Output = savedOutput } out.WriteString("```\n\n") diff --git a/mdl/executor/cmd_contract.go b/mdl/executor/cmd_contract.go index b9890c74..3141cd75 100644 --- a/mdl/executor/cmd_contract.go +++ b/mdl/executor/cmd_contract.go @@ -17,7 +17,6 @@ import ( // showContractEntities handles SHOW CONTRACT ENTITIES FROM Module.Service. func showContractEntities(ctx *ExecContext, name *ast.QualifiedName) error { - e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT ENTITIES FROM Module.Service") } @@ -73,12 +72,11 @@ func showContractEntities(ctx *ExecContext, name *ast.QualifiedName) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.entitySet, r.entityType, r.key, r.props, r.navs, r.summary}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showContractActions handles SHOW CONTRACT ACTIONS FROM Module.Service. func showContractActions(ctx *ExecContext, name *ast.QualifiedName) error { - e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT ACTIONS FROM Module.Service") } @@ -138,7 +136,7 @@ func showContractActions(ctx *ExecContext, name *ast.QualifiedName) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.name, r.params, r.returnType, r.bound}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeContractEntity handles DESCRIBE CONTRACT ENTITY Service.EntityName [FORMAT mdl]. @@ -325,7 +323,7 @@ func parseServiceContract(ctx *ExecContext, name ast.QualifiedName) (*mpr.EdmxDo return nil, "", mdlerrors.NewBackend("list consumed OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, "", mdlerrors.NewBackend("build hierarchy", err) } @@ -480,7 +478,7 @@ func createExternalEntities(ctx *ExecContext, s *ast.CreateExternalEntitiesStmt) targetModule = s.ServiceRef.Module } - module, err := e.findModule(targetModule) + module, err := findModule(ctx, targetModule) if err != nil { return err } @@ -1254,7 +1252,6 @@ func edmToAstDataType(p *mpr.EdmProperty) ast.DataType { // showContractChannels handles SHOW CONTRACT CHANNELS FROM Module.Service. func showContractChannels(ctx *ExecContext, name *ast.QualifiedName) error { - e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT CHANNELS FROM Module.Service") } @@ -1289,12 +1286,11 @@ func showContractChannels(ctx *ExecContext, name *ast.QualifiedName) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.channel, r.operation, r.opID, r.message}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showContractMessages handles SHOW CONTRACT MESSAGES FROM Module.Service. func showContractMessages(ctx *ExecContext, name *ast.QualifiedName) error { - e := ctx.executor if name == nil { return mdlerrors.NewValidation("service name required: SHOW CONTRACT MESSAGES FROM Module.Service") } @@ -1333,7 +1329,7 @@ func showContractMessages(ctx *ExecContext, name *ast.QualifiedName) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.name, r.title, r.contentType, r.props}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeContractMessage handles DESCRIBE CONTRACT MESSAGE Module.Service.MessageName. @@ -1396,7 +1392,7 @@ func parseAsyncAPIContract(ctx *ExecContext, name ast.QualifiedName) (*mpr.Async return nil, "", mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, "", mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_datatransformer.go b/mdl/executor/cmd_datatransformer.go index 5cd19993..882369d9 100644 --- a/mdl/executor/cmd_datatransformer.go +++ b/mdl/executor/cmd_datatransformer.go @@ -21,7 +21,7 @@ func listDataTransformers(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list data transformers", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -54,7 +54,7 @@ func listDataTransformers(ctx *ExecContext, moduleName string) error { Rows: rows, Summary: fmt.Sprintf("(%d data transformers)", len(rows)), } - return e.writeResult(result) + return writeResult(ctx, result) } // describeDataTransformer handles DESCRIBE DATA TRANSFORMER Module.Name. @@ -66,7 +66,7 @@ func describeDataTransformer(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list data transformers", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -121,7 +121,7 @@ func execCreateDataTransformer(ctx *ExecContext, s *ast.CreateDataTransformerStm return err } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } @@ -164,7 +164,7 @@ func execDropDataTransformer(ctx *ExecContext, s *ast.DropDataTransformerStmt) e return mdlerrors.NewBackend("list data transformers", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } diff --git a/mdl/executor/cmd_dbconnection.go b/mdl/executor/cmd_dbconnection.go index 366b3ba1..a2166906 100644 --- a/mdl/executor/cmd_dbconnection.go +++ b/mdl/executor/cmd_dbconnection.go @@ -25,14 +25,14 @@ func createDatabaseConnection(ctx *ExecContext, stmt *ast.CreateDatabaseConnecti return mdlerrors.NewValidation("module name required: use CREATE DATABASE CONNECTION Module.ConnectionName") } - module, err := e.findModule(stmt.Name.Module) + module, err := findModule(ctx, stmt.Name.Module) if err != nil { return err } // Check for existing connection existing, _ := e.reader.ListDatabaseConnections() - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) for _, ex := range existing { modID := h.FindModuleID(ex.ContainerID) @@ -118,7 +118,7 @@ func createDatabaseConnection(ctx *ExecContext, stmt *ast.CreateDatabaseConnecti return mdlerrors.NewBackend("create database connection", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created database connection: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } @@ -132,7 +132,7 @@ func showDatabaseConnections(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list database connections", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -176,7 +176,7 @@ func showDatabaseConnections(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath, r.dbType, r.queries}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeDatabaseConnection handles DESCRIBE DATABASE CONNECTION command. @@ -188,7 +188,7 @@ func describeDatabaseConnection(ctx *ExecContext, name ast.QualifiedName) error return mdlerrors.NewBackend("list database connections", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -285,7 +285,7 @@ func resolveConstantDefault(ctx *ExecContext, qualifiedName string) string { if err != nil { return "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } diff --git a/mdl/executor/cmd_diff.go b/mdl/executor/cmd_diff.go index 8631a341..866a0364 100644 --- a/mdl/executor/cmd_diff.go +++ b/mdl/executor/cmd_diff.go @@ -173,7 +173,7 @@ func diffEntity(ctx *ExecContext, s *ast.CreateEntityStmt) (*DiffResult, error) } // Try to find existing entity - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -207,7 +207,7 @@ func diffViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) (*DiffResult, Proposed: viewEntityStmtToMDL(ctx, s), } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -246,7 +246,7 @@ func diffEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) (*DiffResul return result, nil } - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) modName := h.GetModuleName(existingEnum.ContainerID) result.Current = enumerationToMDL(ctx, modName, existingEnum) result.Changes = compareEnumerations(ctx, result.Current, result.Proposed) @@ -263,7 +263,7 @@ func diffAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) (*DiffResul Proposed: associationStmtToMDL(ctx, s), } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -296,7 +296,7 @@ func diffMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) (*DiffResult, e } // Try to find existing microflow - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { result.IsNew = true return result, nil diff --git a/mdl/executor/cmd_diff_local.go b/mdl/executor/cmd_diff_local.go index 8d13f8b6..7703e790 100644 --- a/mdl/executor/cmd_diff_local.go +++ b/mdl/executor/cmd_diff_local.go @@ -267,7 +267,6 @@ func extractUUIDFromPath(path string) string { // bsonToMDL converts BSON content to MDL representation based on type func bsonToMDL(ctx *ExecContext, unitType, unitID string, content []byte) string { - e := ctx.executor var raw map[string]any if err := bson.Unmarshal(content, &raw); err != nil { return fmt.Sprintf("-- Error parsing BSON: %v", err) @@ -283,7 +282,7 @@ func bsonToMDL(ctx *ExecContext, unitType, unitID string, content []byte) string qualifiedName := name if containerID := extractBsonID(raw["$Container"]); containerID != "" { // Try to resolve module name from container - if h, err := e.getHierarchy(); err == nil { + if h, err := getHierarchy(ctx); err == nil { if modName := h.GetModuleName(model.ID(containerID)); modName != "" { qualifiedName = modName + "." + name } else if modName := h.GetModuleName(h.FindModuleID(model.ID(containerID))); modName != "" { @@ -526,7 +525,7 @@ func buildNameLookups(ctx *ExecContext) (map[model.ID]string, map[model.ID]strin if e.reader == nil { return entityNames, microflowNames } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return entityNames, microflowNames } diff --git a/mdl/executor/cmd_domainmodel_elk.go b/mdl/executor/cmd_domainmodel_elk.go index 95c2e3da..946048e9 100644 --- a/mdl/executor/cmd_domainmodel_elk.go +++ b/mdl/executor/cmd_domainmodel_elk.go @@ -79,7 +79,7 @@ func domainModelELK(ctx *ExecContext, name string) error { } moduleName := name - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -168,7 +168,7 @@ func entityFocusELK(ctx *ExecContext, qualifiedName string) error { } moduleName, entityName := parts[0], parts[1] - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -328,7 +328,7 @@ func buildAllEntityNames(ctx *ExecContext) (map[model.ID]string, map[model.ID]st e := ctx.executor allEntityNames := make(map[model.ID]string) allEntityModules := make(map[model.ID]string) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return allEntityNames, allEntityModules } diff --git a/mdl/executor/cmd_entities.go b/mdl/executor/cmd_entities.go index c5c66c32..55ed1e95 100644 --- a/mdl/executor/cmd_entities.go +++ b/mdl/executor/cmd_entities.go @@ -53,7 +53,7 @@ func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error { } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -275,8 +275,8 @@ func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error { return mdlerrors.NewBackend("update entity", err) } // Invalidate caches so updated entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Modified entity: %s\n", s.Name) } else { // Create new entity @@ -284,8 +284,8 @@ func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error { return mdlerrors.NewBackend("create entity", err) } // Invalidate caches so new entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Created entity: %s\n", s.Name) } @@ -321,7 +321,7 @@ func execCreateViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) error { } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -446,8 +446,8 @@ func execCreateViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) error { return mdlerrors.NewBackend("update view entity", err) } // Invalidate caches so updated entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Modified view entity: %s\n", s.Name) } else { // Create new entity @@ -455,8 +455,8 @@ func execCreateViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) error { return mdlerrors.NewBackend("create view entity", err) } // Invalidate caches so new entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Created view entity: %s\n", s.Name) } @@ -471,7 +471,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -598,8 +598,8 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Added attribute '%s' to entity %s\n", a.Name, s.Name) case ast.AlterEntityRenameAttribute: @@ -617,8 +617,8 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("rename attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Renamed attribute '%s' to '%s' on entity %s\n", s.AttributeName, s.NewName, s.Name) case ast.AlterEntityModifyAttribute: @@ -654,8 +654,8 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("modify attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Modified attribute '%s' on entity %s\n", s.AttributeName, s.Name) case ast.AlterEntityDropAttribute: @@ -753,8 +753,8 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Dropped attribute '%s' from entity %s\n", s.AttributeName, s.Name) // Report what was cleaned up on the entity itself @@ -778,7 +778,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("set documentation", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Set documentation on entity %s\n", s.Name) case ast.AlterEntitySetComment: @@ -787,7 +787,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("set comment", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Set comment on entity %s\n", s.Name) case ast.AlterEntitySetPosition: @@ -798,7 +798,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("set position", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Set position of entity %s to (%d, %d)\n", s.Name, s.Position.X, s.Position.Y) case ast.AlterEntityAddIndex: @@ -832,7 +832,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add index", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Added index to entity %s\n", s.Name) case ast.AlterEntityDropIndex: @@ -856,7 +856,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop index", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Dropped index '%s' from entity %s\n", s.IndexName, s.Name) case ast.AlterEntityAddEventHandler: @@ -879,7 +879,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add event handler", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Added event handler %s %s on %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) @@ -905,7 +905,7 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop event handler", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Dropped event handler %s %s from %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) @@ -925,7 +925,7 @@ func execDropEntity(ctx *ExecContext, s *ast.DropEntityStmt) error { } // Find module and entity - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -949,7 +949,7 @@ func execDropEntity(ctx *ExecContext, s *ast.DropEntityStmt) error { if err := e.writer.DeleteEntity(dm.ID, entity.ID); err != nil { return mdlerrors.NewBackend("delete entity", err) } - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Dropped entity: %s\n", s.Name) return nil } diff --git a/mdl/executor/cmd_entities_access.go b/mdl/executor/cmd_entities_access.go index a6967325..a43025da 100644 --- a/mdl/executor/cmd_entities_access.go +++ b/mdl/executor/cmd_entities_access.go @@ -166,9 +166,9 @@ func formatAccessRuleRights(ctx *ExecContext, rule *domainmodel.AccessRule, attr // for the given roles. Returns a string like " Result: CREATE, READ (Name, Price)\n". func formatAccessRuleResult(ctx *ExecContext, moduleName, entityName string, roleNames []string) string { e := ctx.executor - e.invalidateDomainModelsCache() + invalidateDomainModelsCache(ctx) - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return "" } diff --git a/mdl/executor/cmd_entities_describe.go b/mdl/executor/cmd_entities_describe.go index 487933f7..1c95d494 100644 --- a/mdl/executor/cmd_entities_describe.go +++ b/mdl/executor/cmd_entities_describe.go @@ -155,7 +155,7 @@ func showEntities(ctx *ExecContext, moduleName string) error { } result.Rows = append(result.Rows, rowData) } - return e.writeResult(result) + return writeResult(ctx, result) } // showEntity handles SHOW ENTITY command. @@ -165,7 +165,7 @@ func showEntity(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewValidation("entity name required") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -219,7 +219,7 @@ func showEntity(ctx *ExecContext, name *ast.QualifiedName) error { // describeEntity handles DESCRIBE ENTITY command. func describeEntity(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -487,7 +487,7 @@ func resolveMicroflowByName(ctx *ExecContext, qualifiedName string) (model.ID, e return "", mdlerrors.NewBackend("list microflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "", mdlerrors.NewBackend("build hierarchy", err) } @@ -511,7 +511,7 @@ func lookupMicroflowName(ctx *ExecContext, mfID model.ID) string { return "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } diff --git a/mdl/executor/cmd_enumerations.go b/mdl/executor/cmd_enumerations.go index 9b620a87..583d0c41 100644 --- a/mdl/executor/cmd_enumerations.go +++ b/mdl/executor/cmd_enumerations.go @@ -34,7 +34,7 @@ func execCreateEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) error } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -76,7 +76,7 @@ func execCreateEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) error } // Invalidate hierarchy cache so the new enumeration's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created enumeration: %s\n", s.Name) return nil @@ -91,7 +91,7 @@ func findEnumeration(ctx *ExecContext, moduleName, enumName string) *model.Enume return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -134,7 +134,7 @@ func execDropEnumeration(ctx *ExecContext, s *ast.DropEnumerationStmt) error { for _, enum := range enums { if enum.Name == s.Name.Name { // Check module matches - module, err := e.findModuleByID(enum.ContainerID) + module, err := findModuleByID(ctx, enum.ContainerID) if err == nil && (s.Name.Module == "" || module.Name == s.Name.Module) { if err := e.writer.DeleteEnumeration(enum.ID); err != nil { return mdlerrors.NewBackend("delete enumeration", err) @@ -158,7 +158,7 @@ func showEnumerations(ctx *ExecContext, moduleName string) error { } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -197,7 +197,7 @@ func showEnumerations(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath, r.values}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showEnumerations is an Executor method wrapper for callers not yet migrated. @@ -214,7 +214,7 @@ func describeEnumeration(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list enumerations", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_export_mappings.go b/mdl/executor/cmd_export_mappings.go index ca1eb09b..1f9d35cb 100644 --- a/mdl/executor/cmd_export_mappings.go +++ b/mdl/executor/cmd_export_mappings.go @@ -27,7 +27,7 @@ func showExportMappings(ctx *ExecContext, inModule string) error { return mdlerrors.NewBackend("list export mappings", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -76,7 +76,7 @@ func showExportMappings(ctx *ExecContext, inModule string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.name, r.schemaSource, r.elementCount}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showExportMappings is a wrapper for callers that still use an Executor receiver. @@ -103,7 +103,7 @@ func describeExportMapping(ctx *ExecContext, name ast.QualifiedName) error { fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", strings.ReplaceAll(em.Documentation, "\n", "\n * ")) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -195,7 +195,7 @@ func execCreateExportMapping(ctx *ExecContext, s *ast.CreateExportMappingStmt) e return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } diff --git a/mdl/executor/cmd_features.go b/mdl/executor/cmd_features.go index 2226ff05..07929a4b 100644 --- a/mdl/executor/cmd_features.go +++ b/mdl/executor/cmd_features.go @@ -103,8 +103,6 @@ func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { } func showFeaturesAll(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer) error { - e := ctx.executor - features := reg.FeaturesForVersion(pv) if len(features) == 0 { fmt.Fprintf(ctx.Output, "No features found for version %s\n", pv) @@ -135,12 +133,10 @@ func showFeaturesAll(ctx *ExecContext, reg *versions.Registry, pv versions.SemVe tr.Rows = append(tr.Rows, []any{f.DisplayName(), avail, fmt.Sprintf("%s", f.MinVersion), notes}) } tr.Summary = fmt.Sprintf("(%d available, %d not available in %s)", available, unavailable, pv) - return e.writeResult(tr) + return writeResult(ctx, tr) } func showFeaturesInArea(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer, area string) error { - e := ctx.executor - features := reg.FeaturesInArea(area, pv) if len(features) == 0 { // Check if the area exists at all. @@ -169,12 +165,10 @@ func showFeaturesInArea(ctx *ExecContext, reg *versions.Registry, pv versions.Se } tr.Rows = append(tr.Rows, []any{f.DisplayName(), avail, fmt.Sprintf("%s", f.MinVersion), notes}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } func showFeaturesAddedSince(ctx *ExecContext, reg *versions.Registry, sinceV versions.SemVer) error { - e := ctx.executor - added := reg.FeaturesAddedSince(sinceV) if len(added) == 0 { fmt.Fprintf(ctx.Output, "No new features found since %s\n", sinceV) @@ -197,5 +191,5 @@ func showFeaturesAddedSince(ctx *ExecContext, reg *versions.Registry, sinceV ver } tr.Rows = append(tr.Rows, []any{f.DisplayName(), f.Area, fmt.Sprintf("%s", f.MinVersion), notes}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } diff --git a/mdl/executor/cmd_folders.go b/mdl/executor/cmd_folders.go index f9442d8b..af2a630f 100644 --- a/mdl/executor/cmd_folders.go +++ b/mdl/executor/cmd_folders.go @@ -56,7 +56,7 @@ func execDropFolder(ctx *ExecContext, s *ast.DropFolderStmt) error { return mdlerrors.NewNotConnected() } - module, err := e.findModule(s.Module) + module, err := findModule(ctx, s.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Module) } @@ -75,7 +75,7 @@ func execDropFolder(ctx *ExecContext, s *ast.DropFolderStmt) error { return mdlerrors.NewBackend(fmt.Sprintf("delete folder '%s'", s.FolderPath), err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped folder: '%s' in %s\n", s.FolderPath, s.Module) return nil } @@ -88,7 +88,7 @@ func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error { } // Find the source module - sourceModule, err := e.findModule(s.Name.Module) + sourceModule, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("source module", s.Name.Module) } @@ -107,7 +107,7 @@ func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error { // Determine target module var targetModule *model.Module if s.TargetModule != "" { - targetModule, err = e.findModule(s.TargetModule) + targetModule, err = findModule(ctx, s.TargetModule) if err != nil { return mdlerrors.NewNotFound("target module", s.TargetModule) } @@ -118,7 +118,7 @@ func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error { // Resolve target container var targetContainerID model.ID if s.TargetFolder != "" { - targetContainerID, err = e.resolveFolder(targetModule.ID, s.TargetFolder) + targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.TargetFolder) if err != nil { return mdlerrors.NewBackend("resolve target folder", err) } @@ -131,7 +131,7 @@ func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error { return mdlerrors.NewBackend("move folder", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) target := targetModule.Name if s.TargetFolder != "" { diff --git a/mdl/executor/cmd_fragments.go b/mdl/executor/cmd_fragments.go index 7b6ead8d..c7fb8b94 100644 --- a/mdl/executor/cmd_fragments.go +++ b/mdl/executor/cmd_fragments.go @@ -93,7 +93,7 @@ func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) err return mdlerrors.NewNotConnected() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index 1b84c7d3..14c4e4d6 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -23,7 +23,7 @@ func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStm } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -69,7 +69,7 @@ func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStm } // Invalidate hierarchy cache so the new collection's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created image collection: %s\n", s.Name) return nil @@ -113,7 +113,7 @@ func describeImageCollection(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("image collection", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -223,7 +223,7 @@ func showImageCollections(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list image collections", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -248,7 +248,7 @@ func showImageCollections(ctx *ExecContext, moduleName string) error { } result.Summary = fmt.Sprintf("(%d image collection(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // Executor wrapper for unmigrated callers. diff --git a/mdl/executor/cmd_import.go b/mdl/executor/cmd_import.go index 8399c938..5cf0eb08 100644 --- a/mdl/executor/cmd_import.go +++ b/mdl/executor/cmd_import.go @@ -125,7 +125,7 @@ func resolveImportLinks(ctx *ExecContext, goCtx context.Context, mendixConn *sql return nil, mdlerrors.NewBackend("list domain models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, mdlerrors.NewBackend("get hierarchy", err) } diff --git a/mdl/executor/cmd_import_mappings.go b/mdl/executor/cmd_import_mappings.go index 34fa4b5f..84c7392d 100644 --- a/mdl/executor/cmd_import_mappings.go +++ b/mdl/executor/cmd_import_mappings.go @@ -27,7 +27,7 @@ func showImportMappings(ctx *ExecContext, inModule string) error { return mdlerrors.NewBackend("list import mappings", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -76,7 +76,7 @@ func showImportMappings(ctx *ExecContext, inModule string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.name, r.schemaSource, r.elementCount}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showImportMappings is a wrapper for callers that still use an Executor receiver. @@ -103,7 +103,7 @@ func describeImportMapping(ctx *ExecContext, name ast.QualifiedName) error { fmt.Fprintf(ctx.Output, "/**\n * %s\n */\n", strings.ReplaceAll(im.Documentation, "\n", "\n * ")) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -208,7 +208,7 @@ func execCreateImportMapping(ctx *ExecContext, s *ast.CreateImportMappingStmt) e return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } diff --git a/mdl/executor/cmd_javaactions.go b/mdl/executor/cmd_javaactions.go index 450bd652..45d4954f 100644 --- a/mdl/executor/cmd_javaactions.go +++ b/mdl/executor/cmd_javaactions.go @@ -22,7 +22,7 @@ import ( func showJavaActions(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -64,7 +64,7 @@ func showJavaActions(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showJavaActions is a wrapper for callers that still use an Executor receiver. @@ -266,7 +266,7 @@ func execDropJavaAction(ctx *ExecContext, s *ast.DropJavaActionStmt) error { } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -300,7 +300,7 @@ func execCreateJavaAction(ctx *ExecContext, s *ast.CreateJavaActionStmt) error { } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_javascript_actions.go b/mdl/executor/cmd_javascript_actions.go index 5897bd0c..4e182d61 100644 --- a/mdl/executor/cmd_javascript_actions.go +++ b/mdl/executor/cmd_javascript_actions.go @@ -19,7 +19,7 @@ import ( // showJavaScriptActions handles SHOW JAVASCRIPT ACTIONS command. func showJavaScriptActions(ctx *ExecContext, moduleName string) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -63,7 +63,7 @@ func showJavaScriptActions(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.platform, r.folderPath}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showJavaScriptActions is a wrapper for callers that still use an Executor receiver. diff --git a/mdl/executor/cmd_jsonstructures.go b/mdl/executor/cmd_jsonstructures.go index caea614a..2fb915a0 100644 --- a/mdl/executor/cmd_jsonstructures.go +++ b/mdl/executor/cmd_jsonstructures.go @@ -23,7 +23,7 @@ func showJsonStructures(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list JSON structures", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -67,7 +67,7 @@ func showJsonStructures(ctx *ExecContext, moduleName string) error { for _, r := range rows { tr.Rows = append(tr.Rows, []any{r.qualifiedName, r.elemCount, r.source}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // showJsonStructures is a wrapper for callers that still use an Executor receiver. @@ -78,13 +78,12 @@ func (e *Executor) showJsonStructures(moduleName string) error { // describeJsonStructure handles DESCRIBE JSON STRUCTURE Module.Name. // Output is re-executable CREATE OR REPLACE MDL followed by the element tree as comments. func describeJsonStructure(ctx *ExecContext, name ast.QualifiedName) error { - e := ctx.executor js := findJsonStructure(ctx, name.Module, name.Name) if js == nil { return mdlerrors.NewNotFound("JSON structure", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -192,7 +191,7 @@ func execCreateJsonStructure(ctx *ExecContext, s *ast.CreateJsonStructureStmt) e } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -200,7 +199,7 @@ func execCreateJsonStructure(ctx *ExecContext, s *ast.CreateJsonStructureStmt) e // Resolve folder if specified containerID := module.ID if s.Folder != "" { - folderID, err := e.resolveFolder(module.ID, s.Folder) + folderID, err := resolveFolder(ctx, module.ID, s.Folder) if err != nil { return mdlerrors.NewBackend("resolve folder "+s.Folder, err) } @@ -244,7 +243,7 @@ func execCreateJsonStructure(ctx *ExecContext, s *ast.CreateJsonStructureStmt) e } // Invalidate hierarchy cache - e.invalidateHierarchy() + invalidateHierarchy(ctx) action := "Created" if existing != nil { @@ -282,7 +281,7 @@ func findJsonStructure(ctx *ExecContext, moduleName, structName string) *mpr.Jso return nil } - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) if h == nil { return nil } diff --git a/mdl/executor/cmd_languages.go b/mdl/executor/cmd_languages.go index 5dc602a8..239a7ed9 100644 --- a/mdl/executor/cmd_languages.go +++ b/mdl/executor/cmd_languages.go @@ -12,7 +12,6 @@ import ( // showLanguages lists all languages found in the project's translatable strings. // Requires REFRESH CATALOG FULL to populate the strings table. func showLanguages(ctx *ExecContext) error { - e := ctx.executor if ctx.Catalog == nil { return mdlerrors.NewValidation("no catalog available — run REFRESH CATALOG FULL first") } @@ -48,7 +47,7 @@ func showLanguages(ctx *ExecContext) error { } tr.Rows = append(tr.Rows, []any{lang, count}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // --- Executor method wrapper for backward compatibility --- diff --git a/mdl/executor/cmd_layouts.go b/mdl/executor/cmd_layouts.go index 5b89572d..79ce5d25 100644 --- a/mdl/executor/cmd_layouts.go +++ b/mdl/executor/cmd_layouts.go @@ -16,7 +16,7 @@ import ( func showLayouts(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -61,7 +61,7 @@ func showLayouts(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath, r.layoutType}) } - return e.writeResult(result) + return writeResult(ctx, result) } func (e *Executor) showLayouts(moduleName string) error { diff --git a/mdl/executor/cmd_lint.go b/mdl/executor/cmd_lint.go index 8212806c..8e556c21 100644 --- a/mdl/executor/cmd_lint.go +++ b/mdl/executor/cmd_lint.go @@ -28,7 +28,7 @@ func execLint(ctx *ExecContext, s *ast.LintStmt) error { // Ensure catalog is built if ctx.Catalog == nil { fmt.Fprintln(ctx.Output, "Building catalog for linting...") - if err := e.buildCatalog(false); err != nil { + if err := buildCatalog(ctx, false); err != nil { return mdlerrors.NewBackend("build catalog", err) } } diff --git a/mdl/executor/cmd_mermaid.go b/mdl/executor/cmd_mermaid.go index 758756d0..ba72749c 100644 --- a/mdl/executor/cmd_mermaid.go +++ b/mdl/executor/cmd_mermaid.go @@ -51,7 +51,7 @@ func (e *Executor) DescribeMermaid(objectType, name string) error { // domainModelToMermaid generates a Mermaid erDiagram for a module's domain model. func domainModelToMermaid(ctx *ExecContext, moduleName string) error { e := ctx.executor - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -69,7 +69,7 @@ func domainModelToMermaid(ctx *ExecContext, moduleName string) error { // Also load entities from all modules for cross-module associations allEntityNames := make(map[model.ID]string) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err == nil { domainModels, _ := e.reader.ListDomainModels() for _, otherDM := range domainModels { @@ -191,7 +191,7 @@ func domainModelToMermaid(ctx *ExecContext, moduleName string) error { // microflowToMermaid generates a Mermaid flowchart for a microflow. func microflowToMermaid(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -370,7 +370,7 @@ func renderMicroflowMermaid(ctx *ExecContext, mf *microflows.Microflow, entityNa // pageToMermaid generates a Mermaid block diagram for a page's widget structure. func pageToMermaid(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_microflow_elk.go b/mdl/executor/cmd_microflow_elk.go index 7b1fb21f..f8a7ab32 100644 --- a/mdl/executor/cmd_microflow_elk.go +++ b/mdl/executor/cmd_microflow_elk.go @@ -74,7 +74,7 @@ func microflowELK(ctx *ExecContext, name string) error { qn := ast.QualifiedName{Module: parts[0], Name: parts[1]} - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_microflows_create.go b/mdl/executor/cmd_microflows_create.go index 1e54c595..983f7def 100644 --- a/mdl/executor/cmd_microflows_create.go +++ b/mdl/executor/cmd_microflows_create.go @@ -40,7 +40,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -48,7 +48,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { // Resolve folder if specified containerID := module.ID if s.Folder != "" { - folderID, err := e.resolveFolder(module.ID, s.Folder) + folderID, err := resolveFolder(ctx, module.ID, s.Folder) if err != nil { return mdlerrors.NewBackend("resolve folder "+s.Folder, err) } @@ -202,7 +202,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { } } // Get hierarchy for resolving page/microflow references - hierarchy, _ := e.getHierarchy() + hierarchy, _ := getHierarchy(ctx) restServices, _ := loadRestServices(ctx) @@ -250,6 +250,6 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { e.trackCreatedMicroflow(s.Name.Module, s.Name.Name, mf.ID, containerID, returnEntityName) // Invalidate hierarchy cache so the new microflow's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) return nil } diff --git a/mdl/executor/cmd_microflows_drop.go b/mdl/executor/cmd_microflows_drop.go index 5bc1d1b9..8e8368fd 100644 --- a/mdl/executor/cmd_microflows_drop.go +++ b/mdl/executor/cmd_microflows_drop.go @@ -18,7 +18,7 @@ func execDropMicroflow(ctx *ExecContext, s *ast.DropMicroflowStmt) error { } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -41,7 +41,7 @@ func execDropMicroflow(ctx *ExecContext, s *ast.DropMicroflowStmt) error { if e.cache != nil && e.cache.createdMicroflows != nil { delete(e.cache.createdMicroflows, qualifiedName) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped microflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } diff --git a/mdl/executor/cmd_microflows_format_action.go b/mdl/executor/cmd_microflows_format_action.go index d0a4f247..d2667885 100644 --- a/mdl/executor/cmd_microflows_format_action.go +++ b/mdl/executor/cmd_microflows_format_action.go @@ -503,7 +503,7 @@ func formatAction( pages, _ := e.reader.ListPages() for _, p := range pages { if p.ID == a.PageID { - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) if h != nil { pageName = h.GetQualifiedName(p.ContainerID, p.Name) } diff --git a/mdl/executor/cmd_microflows_show.go b/mdl/executor/cmd_microflows_show.go index 5bc4c014..4a9db700 100644 --- a/mdl/executor/cmd_microflows_show.go +++ b/mdl/executor/cmd_microflows_show.go @@ -19,7 +19,7 @@ import ( func showMicroflows(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -77,14 +77,14 @@ func showMicroflows(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.excluded, r.folderPath, r.params, r.activities, r.complexity, r.returnType}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showNanoflows handles SHOW NANOFLOWS command. func showNanoflows(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -142,7 +142,7 @@ func showNanoflows(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.excluded, r.folderPath, r.params, r.activities, r.complexity, r.returnType}) } - return e.writeResult(result) + return writeResult(ctx, result) } // countNanoflowActivities counts meaningful activities in a nanoflow. @@ -183,7 +183,7 @@ func calculateNanoflowComplexity(nf *microflows.Nanoflow) int { func describeMicroflow(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -311,7 +311,7 @@ func describeMicroflow(ctx *ExecContext, name ast.QualifiedName) error { // with activities and control flows listed as comments. func describeNanoflow(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -428,7 +428,7 @@ func describeNanoflow(ctx *ExecContext, name ast.QualifiedName) error { // along with a source map mapping node IDs to line ranges. func describeMicroflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "", nil, mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_module_overview.go b/mdl/executor/cmd_module_overview.go index b67875e2..4ca2c2cf 100644 --- a/mdl/executor/cmd_module_overview.go +++ b/mdl/executor/cmd_module_overview.go @@ -54,7 +54,7 @@ func ModuleOverview(ctx *ExecContext) error { } // Ensure catalog is built with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return mdlerrors.NewBackend("build catalog", err) } diff --git a/mdl/executor/cmd_modules.go b/mdl/executor/cmd_modules.go index 63e43849..78188184 100644 --- a/mdl/executor/cmd_modules.go +++ b/mdl/executor/cmd_modules.go @@ -85,7 +85,7 @@ func execDropModule(ctx *ExecContext, s *ast.DropModuleStmt) error { } // Build set of all container IDs belonging to this module (including nested folders) - moduleContainers := e.getModuleContainers(targetModule.ID) + moduleContainers := getModuleContainers(ctx, targetModule.ID) // Counters for summary var nEnums, nEntities, nAssocs, nMicroflows, nNanoflows, nPages, nSnippets, nLayouts, nConstants, nJavaActions, nServices, nBizEvents, nDbConns int @@ -345,7 +345,8 @@ func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { // getModuleContainers returns a set of all container IDs that belong to a module // (including nested folders). -func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { +func getModuleContainers(ctx *ExecContext, moduleID model.ID) map[model.ID]bool { + e := ctx.executor containers := make(map[model.ID]bool) containers[moduleID] = true @@ -382,21 +383,27 @@ func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { return containers } +// Executor method wrapper for getModuleContainers — kept during migration. +func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { + return getModuleContainers(e.newExecContext(context.Background()), moduleID) +} + // showModules handles SHOW MODULES command. -func (e *Executor) showModules() error { +func showModules(ctx *ExecContext) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } // Always get fresh module list and update cache - e.invalidateModuleCache() - modules, err := e.getModulesFromCache() + invalidateModuleCache(ctx) + modules, err := getModulesFromCache(ctx) if err != nil { return mdlerrors.NewBackend("list modules", err) } // Get hierarchy for module resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -572,11 +579,12 @@ func (e *Executor) showModules() error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.name, r.source, r.entities, r.enums, r.pages, r.snippets, r.microflows, r.nanoflows, r.workflows, r.constants, r.javaActions, r.pubRest, r.pubOData, r.conOData, r.bizEvents, r.extDb}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeModule handles DESCRIBE MODULE [WITH ALL] command. -func (e *Executor) describeModule(moduleName string, withAll bool) error { +func describeModule(ctx *ExecContext, moduleName string, withAll bool) error { + e := ctx.executor if e.reader == nil { return mdlerrors.NewNotConnected() } @@ -600,25 +608,25 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { } // Output basic CREATE MODULE statement - fmt.Fprintf(e.output, "CREATE MODULE %s;\n", targetModule.Name) + fmt.Fprintf(ctx.Output, "CREATE MODULE %s;\n", targetModule.Name) if !withAll { - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } // Get all containers belonging to this module (including nested folders) - moduleContainers := e.getModuleContainers(targetModule.ID) + moduleContainers := getModuleContainers(ctx, targetModule.ID) // Output separator - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Output enumerations in this module (no dependencies between enums) if enums, err := e.reader.ListEnumerations(); err == nil { for _, enum := range enums { if moduleContainers[enum.ContainerID] { - if err := e.describeEnumeration(ast.QualifiedName{Module: moduleName, Name: enum.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeEnumeration(ctx, ast.QualifiedName{Module: moduleName, Name: enum.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -628,8 +636,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if constants, err := e.reader.ListConstants(); err == nil { for _, c := range constants { if moduleContainers[c.ContainerID] { - if err := e.outputConstantMDL(c, moduleName); err == nil { - fmt.Fprintln(e.output) + if err := outputConstantMDL(ctx, c, moduleName); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -639,19 +647,19 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { // and associations after all entities if dm, err := e.reader.GetDomainModel(targetModule.ID); err == nil { // Topologically sort entities by generalization (inheritance) - sortedEntities := e.sortEntitiesByGeneralization(dm.Entities, moduleName) + sortedEntities := sortEntitiesByGeneralization(dm.Entities, moduleName) // Output entities in sorted order for _, entity := range sortedEntities { - if err := e.describeEntity(ast.QualifiedName{Module: moduleName, Name: entity.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeEntity(ctx, ast.QualifiedName{Module: moduleName, Name: entity.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } // Output associations (after all entities are defined) for _, assoc := range dm.Associations { - if err := e.describeAssociation(ast.QualifiedName{Module: moduleName, Name: assoc.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeAssociation(ctx, ast.QualifiedName{Module: moduleName, Name: assoc.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -660,8 +668,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if mfs, err := e.reader.ListMicroflows(); err == nil { for _, mf := range mfs { if moduleContainers[mf.ContainerID] { - if err := e.describeMicroflow(ast.QualifiedName{Module: moduleName, Name: mf.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeMicroflow(ctx, ast.QualifiedName{Module: moduleName, Name: mf.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -671,8 +679,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if jaList, err := e.reader.ListJavaActions(); err == nil { for _, ja := range jaList { if moduleContainers[ja.ContainerID] { - if err := e.describeJavaAction(ast.QualifiedName{Module: moduleName, Name: ja.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeJavaAction(ctx, ast.QualifiedName{Module: moduleName, Name: ja.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -682,8 +690,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if pageList, err := e.reader.ListPages(); err == nil { for _, p := range pageList { if moduleContainers[p.ContainerID] { - if err := e.describePage(ast.QualifiedName{Module: moduleName, Name: p.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describePage(ctx, ast.QualifiedName{Module: moduleName, Name: p.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -693,8 +701,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if snippets, err := e.reader.ListSnippets(); err == nil { for _, s := range snippets { if moduleContainers[s.ContainerID] { - if err := e.describeSnippet(ast.QualifiedName{Module: moduleName, Name: s.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeSnippet(ctx, ast.QualifiedName{Module: moduleName, Name: s.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -704,8 +712,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if layouts, err := e.reader.ListLayouts(); err == nil { for _, l := range layouts { if moduleContainers[l.ContainerID] { - if err := e.describeLayout(ast.QualifiedName{Module: moduleName, Name: l.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeLayout(ctx, ast.QualifiedName{Module: moduleName, Name: l.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -715,8 +723,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if conns, err := e.reader.ListDatabaseConnections(); err == nil { for _, conn := range conns { if moduleContainers[conn.ContainerID] { - if err := e.outputDatabaseConnectionMDL(conn, moduleName); err == nil { - fmt.Fprintln(e.output) + if err := outputDatabaseConnectionMDL(ctx, conn, moduleName); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -726,15 +734,15 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if services, err := e.reader.ListBusinessEventServices(); err == nil { for _, svc := range services { if moduleContainers[svc.ContainerID] { - if err := e.describeBusinessEventService(ast.QualifiedName{Module: moduleName, Name: svc.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeBusinessEventService(ctx, ast.QualifiedName{Module: moduleName, Name: svc.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } } // Get hierarchy for folder path resolution (used by OData sections below) - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) // Output consumed OData services (clients) if services, err := e.reader.ListConsumedODataServices(); err == nil { @@ -744,8 +752,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if h != nil { folderPath = h.BuildFolderPath(svc.ContainerID) } - if err := e.outputConsumedODataServiceMDL(svc, moduleName, folderPath); err == nil { - fmt.Fprintln(e.output) + if err := outputConsumedODataServiceMDL(ctx, svc, moduleName, folderPath); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -759,8 +767,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if h != nil { folderPath = h.BuildFolderPath(svc.ContainerID) } - if err := e.outputPublishedODataServiceMDL(svc, moduleName, folderPath); err == nil { - fmt.Fprintln(e.output) + if err := outputPublishedODataServiceMDL(ctx, svc, moduleName, folderPath); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -773,8 +781,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if models, err := e.reader.ListAgentEditorModels(); err == nil { for _, m := range models { if moduleContainers[m.ContainerID] { - if err := e.describeAgentEditorModel(ast.QualifiedName{Module: moduleName, Name: m.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeAgentEditorModel(ctx, ast.QualifiedName{Module: moduleName, Name: m.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -782,8 +790,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if kbs, err := e.reader.ListAgentEditorKnowledgeBases(); err == nil { for _, kb := range kbs { if moduleContainers[kb.ContainerID] { - if err := e.describeAgentEditorKnowledgeBase(ast.QualifiedName{Module: moduleName, Name: kb.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeAgentEditorKnowledgeBase(ctx, ast.QualifiedName{Module: moduleName, Name: kb.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -791,8 +799,8 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if svcs, err := e.reader.ListAgentEditorConsumedMCPServices(); err == nil { for _, svc := range svcs { if moduleContainers[svc.ContainerID] { - if err := e.describeAgentEditorConsumedMCPService(ast.QualifiedName{Module: moduleName, Name: svc.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeAgentEditorConsumedMCPService(ctx, ast.QualifiedName{Module: moduleName, Name: svc.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } @@ -800,20 +808,20 @@ func (e *Executor) describeModule(moduleName string, withAll bool) error { if agents, err := e.reader.ListAgentEditorAgents(); err == nil { for _, a := range agents { if moduleContainers[a.ContainerID] { - if err := e.describeAgentEditorAgent(ast.QualifiedName{Module: moduleName, Name: a.Name}); err == nil { - fmt.Fprintln(e.output) + if err := describeAgentEditorAgent(ctx, ast.QualifiedName{Module: moduleName, Name: a.Name}); err == nil { + fmt.Fprintln(ctx.Output) } } } } - fmt.Fprintln(e.output, "/") + fmt.Fprintln(ctx.Output, "/") return nil } // sortEntitiesByGeneralization returns entities sorted so base entities come before derived entities. // Uses topological sort based on GeneralizationRef (parent entity reference). -func (e *Executor) sortEntitiesByGeneralization(entities []*domainmodel.Entity, moduleName string) []*domainmodel.Entity { +func sortEntitiesByGeneralization(entities []*domainmodel.Entity, moduleName string) []*domainmodel.Entity { if len(entities) <= 1 { return entities } @@ -905,3 +913,13 @@ func (e *Executor) sortEntitiesByGeneralization(entities []*domainmodel.Entity, return sorted } + +// Executor method wrappers for callers in unmigrated files. + +func (e *Executor) showModules() error { + return showModules(e.newExecContext(context.Background())) +} + +func (e *Executor) describeModule(moduleName string, withAll bool) error { + return describeModule(e.newExecContext(context.Background()), moduleName, withAll) +} diff --git a/mdl/executor/cmd_move.go b/mdl/executor/cmd_move.go index 2baf8790..de0d421d 100644 --- a/mdl/executor/cmd_move.go +++ b/mdl/executor/cmd_move.go @@ -20,7 +20,7 @@ func execMove(ctx *ExecContext, s *ast.MoveStmt) error { } // Find the source module - sourceModule, err := e.findModule(s.Name.Module) + sourceModule, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewBackend("find source module", err) } @@ -29,7 +29,7 @@ func execMove(ctx *ExecContext, s *ast.MoveStmt) error { var targetModule *model.Module isCrossModuleMove := false if s.TargetModule != "" { - targetModule, err = e.findModule(s.TargetModule) + targetModule, err = findModule(ctx, s.TargetModule) if err != nil { return mdlerrors.NewBackend("find target module", err) } @@ -46,7 +46,7 @@ func execMove(ctx *ExecContext, s *ast.MoveStmt) error { // Resolve target container (folder or module root) var targetContainerID model.ID if s.Folder != "" { - targetContainerID, err = e.resolveFolder(targetModule.ID, s.Folder) + targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.Folder) if err != nil { return mdlerrors.NewBackend("resolve target folder", err) } @@ -120,7 +120,7 @@ func movePage(ctx *ExecContext, name ast.QualifiedName, targetContainerID model. return mdlerrors.NewBackend("list pages", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -151,7 +151,7 @@ func moveMicroflow(ctx *ExecContext, name ast.QualifiedName, targetContainerID m return mdlerrors.NewBackend("list microflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -182,7 +182,7 @@ func moveSnippet(ctx *ExecContext, name ast.QualifiedName, targetContainerID mod return mdlerrors.NewBackend("list snippets", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -213,7 +213,7 @@ func moveNanoflow(ctx *ExecContext, name ast.QualifiedName, targetContainerID mo return mdlerrors.NewBackend("list nanoflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -337,7 +337,7 @@ func moveConstant(ctx *ExecContext, name ast.QualifiedName, targetContainerID mo return mdlerrors.NewBackend("list constants", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -366,7 +366,7 @@ func moveDatabaseConnection(ctx *ExecContext, name ast.QualifiedName, targetCont return mdlerrors.NewBackend("list database connections", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_navigation.go b/mdl/executor/cmd_navigation.go index 9fa5a4ab..5e1994a4 100644 --- a/mdl/executor/cmd_navigation.go +++ b/mdl/executor/cmd_navigation.go @@ -161,7 +161,7 @@ func showNavigation(ctx *ExecContext) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.name, r.kind, r.homePage, r.loginPage, r.menuItems, r.roleHomes}) } - return e.writeResult(result) + return writeResult(ctx, result) } // Executor wrapper for unmigrated callers. diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index e78b901d..ac0bb78d 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -44,7 +44,7 @@ func showODataClients(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list consumed OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -97,7 +97,7 @@ func showODataClients(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.version, r.odataVer, r.url, r.validated}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeODataClient handles DESCRIBE ODATA CLIENT command. @@ -109,7 +109,7 @@ func describeODataClient(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list consumed OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -228,7 +228,7 @@ func showODataServices(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list published OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -278,7 +278,7 @@ func showODataServices(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.path, r.version, r.odataVer, r.entitySets, r.authTypes}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeODataService handles DESCRIBE ODATA SERVICE command. @@ -290,7 +290,7 @@ func describeODataService(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list published OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -468,7 +468,7 @@ func showExternalEntities(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list domain models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -522,7 +522,7 @@ func showExternalEntities(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.service, r.entitySet, r.remoteName, r.countable}) } - return e.writeResult(result) + return writeResult(ctx, result) } // showExternalActions handles SHOW EXTERNAL ACTIONS [IN module] command. @@ -540,7 +540,7 @@ func showExternalActions(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list nanoflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -657,7 +657,7 @@ func showExternalActions(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.service, r.actionName, r.params, r.usedBy}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeExternalEntity handles DESCRIBE EXTERNAL ENTITY command. @@ -669,7 +669,7 @@ func describeExternalEntity(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list domain models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -767,7 +767,7 @@ func execCreateExternalEntity(ctx *ExecContext, s *ast.CreateExternalEntityStmt) } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -871,7 +871,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error return mdlerrors.NewValidation("module name required: use CREATE ODATA CLIENT Module.Name (...)") } - module, err := e.findModule(stmt.Name.Module) + module, err := findModule(ctx, stmt.Name.Module) if err != nil { return err } @@ -879,7 +879,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error // Check if client already exists services, err := e.reader.ListConsumedODataServices() if err == nil { - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) for _, svc := range services { modID := h.FindModuleID(svc.ContainerID) modName := h.GetModuleName(modID) @@ -955,7 +955,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error if err := e.writer.UpdateConsumedODataService(svc); err != nil { return mdlerrors.NewBackend("update OData client", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Modified OData client: %s.%s\n", modName, svc.Name) return nil } @@ -967,7 +967,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error // Resolve folder if specified containerID := module.ID if stmt.Folder != "" { - folderID, err := e.resolveFolder(module.ID, stmt.Folder) + folderID, err := resolveFolder(ctx, module.ID, stmt.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder %s", stmt.Folder), err) } @@ -1035,7 +1035,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error if err := e.writer.CreateConsumedODataService(newSvc); err != nil { return mdlerrors.NewBackend("create OData client", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created OData client: %s.%s\n", stmt.Name.Module, stmt.Name.Name) if newSvc.Metadata != "" { // Parse to show summary @@ -1065,7 +1065,7 @@ func alterODataClient(ctx *ExecContext, stmt *ast.AlterODataClientStmt) error { return mdlerrors.NewBackend("list consumed OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1134,7 +1134,7 @@ func alterODataClient(ctx *ExecContext, stmt *ast.AlterODataClientStmt) error { if err := e.writer.UpdateConsumedODataService(svc); err != nil { return mdlerrors.NewBackend("alter OData client", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Altered OData client: %s.%s\n", modName, svc.Name) return nil } @@ -1156,7 +1156,7 @@ func dropODataClient(ctx *ExecContext, stmt *ast.DropODataClientStmt) error { return mdlerrors.NewBackend("list consumed OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1168,7 +1168,7 @@ func dropODataClient(ctx *ExecContext, stmt *ast.DropODataClientStmt) error { if err := e.writer.DeleteConsumedODataService(svc.ID); err != nil { return mdlerrors.NewBackend("drop OData client", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped OData client: %s.%s\n", modName, svc.Name) return nil } @@ -1189,7 +1189,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro return mdlerrors.NewValidation("module name required: use CREATE ODATA SERVICE Module.Name (...)") } - module, err := e.findModule(stmt.Name.Module) + module, err := findModule(ctx, stmt.Name.Module) if err != nil { return err } @@ -1197,7 +1197,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro // Check if service already exists services, err := e.reader.ListPublishedODataServices() if err == nil { - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) for _, svc := range services { modID := h.FindModuleID(svc.ContainerID) modName := h.GetModuleName(modID) @@ -1232,7 +1232,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro if err := e.writer.UpdatePublishedODataService(svc); err != nil { return mdlerrors.NewBackend("update OData service", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Modified OData service: %s.%s\n", modName, svc.Name) return nil } @@ -1244,7 +1244,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro // Resolve folder if specified containerID := module.ID if stmt.Folder != "" { - folderID, err := e.resolveFolder(module.ID, stmt.Folder) + folderID, err := resolveFolder(ctx, module.ID, stmt.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder %s", stmt.Folder), err) } @@ -1276,7 +1276,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro if err := e.writer.CreatePublishedODataService(newSvc); err != nil { return mdlerrors.NewBackend("create OData service", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created OData service: %s.%s\n", stmt.Name.Module, stmt.Name.Name) return nil } @@ -1294,7 +1294,7 @@ func alterODataService(ctx *ExecContext, stmt *ast.AlterODataServiceStmt) error return mdlerrors.NewBackend("list published OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1329,7 +1329,7 @@ func alterODataService(ctx *ExecContext, stmt *ast.AlterODataServiceStmt) error if err := e.writer.UpdatePublishedODataService(svc); err != nil { return mdlerrors.NewBackend("alter OData service", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Altered OData service: %s.%s\n", modName, svc.Name) return nil } @@ -1351,7 +1351,7 @@ func dropODataService(ctx *ExecContext, stmt *ast.DropODataServiceStmt) error { return mdlerrors.NewBackend("list published OData services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1363,7 +1363,7 @@ func dropODataService(ctx *ExecContext, stmt *ast.DropODataServiceStmt) error { if err := e.writer.DeletePublishedODataService(svc.ID); err != nil { return mdlerrors.NewBackend("drop OData service", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped OData service: %s.%s\n", modName, svc.Name) return nil } diff --git a/mdl/executor/cmd_page_wireframe.go b/mdl/executor/cmd_page_wireframe.go index d7e46fed..31204839 100644 --- a/mdl/executor/cmd_page_wireframe.go +++ b/mdl/executor/cmd_page_wireframe.go @@ -102,7 +102,7 @@ func PageWireframeJSON(ctx *ExecContext, name string) error { qn := ast.QualifiedName{Module: parts[0], Name: parts[1]} - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -209,7 +209,7 @@ func SnippetWireframeJSON(ctx *ExecContext, name string) error { return mdlerrors.NewNotConnected() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_pages_builder.go b/mdl/executor/cmd_pages_builder.go index 219fe931..2c06f718 100644 --- a/mdl/executor/cmd_pages_builder.go +++ b/mdl/executor/cmd_pages_builder.go @@ -224,8 +224,7 @@ func (pb *pageBuilder) resolveEntity(entityRef ast.QualifiedName) (model.ID, err // getModuleID returns the module ID for any container by using the hierarchy. // Deprecated: prefer using getHierarchy().FindModuleID() directly. func getModuleID(ctx *ExecContext, containerID model.ID) model.ID { - e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return containerID } @@ -235,8 +234,7 @@ func getModuleID(ctx *ExecContext, containerID model.ID) model.ID { // getModuleName returns the module name for a module ID. // Deprecated: prefer using getHierarchy().GetModuleName() directly. func getModuleName(ctx *ExecContext, moduleID model.ID) string { - e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } diff --git a/mdl/executor/cmd_pages_create_v3.go b/mdl/executor/cmd_pages_create_v3.go index eaea1d0c..afc85f60 100644 --- a/mdl/executor/cmd_pages_create_v3.go +++ b/mdl/executor/cmd_pages_create_v3.go @@ -31,7 +31,7 @@ func execCreatePageV3(ctx *ExecContext, s *ast.CreatePageStmtV3) error { } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("find module %s", s.Name.Module), err) } @@ -93,7 +93,7 @@ func execCreatePageV3(ctx *ExecContext, s *ast.CreatePageStmtV3) error { e.trackCreatedPage(s.Name.Module, s.Name.Name, page.ID, moduleID) // Invalidate hierarchy cache so the new page's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created page %s\n", s.Name.String()) return nil @@ -107,7 +107,7 @@ func execCreateSnippetV3(ctx *ExecContext, s *ast.CreateSnippetStmtV3) error { } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("find module %s", s.Name.Module), err) } @@ -162,7 +162,7 @@ func execCreateSnippetV3(ctx *ExecContext, s *ast.CreateSnippetStmtV3) error { e.trackCreatedSnippet(s.Name.Module, s.Name.Name, snippet.ID, moduleID) // Invalidate hierarchy cache so the new snippet's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created snippet %s\n", s.Name.String()) return nil diff --git a/mdl/executor/cmd_pages_describe.go b/mdl/executor/cmd_pages_describe.go index d093c401..ce54a521 100644 --- a/mdl/executor/cmd_pages_describe.go +++ b/mdl/executor/cmd_pages_describe.go @@ -24,7 +24,7 @@ import ( func describePage(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -180,7 +180,7 @@ func formatParametersV3(params []string) []string { func describeSnippet(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -264,7 +264,7 @@ func describeSnippet(ctx *ExecContext, name ast.QualifiedName) error { func describeLayout(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -445,7 +445,7 @@ func resolveLayoutName(ctx *ExecContext, layoutID model.ID) string { return string(layoutID) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return string(layoutID) } diff --git a/mdl/executor/cmd_pages_describe_output.go b/mdl/executor/cmd_pages_describe_output.go index 84c27ac5..e731dae9 100644 --- a/mdl/executor/cmd_pages_describe_output.go +++ b/mdl/executor/cmd_pages_describe_output.go @@ -976,7 +976,7 @@ func getPageQualifiedName(ctx *ExecContext, pageID model.ID) string { if err != nil { return "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } diff --git a/mdl/executor/cmd_pages_show.go b/mdl/executor/cmd_pages_show.go index 4c6618dc..23912d75 100644 --- a/mdl/executor/cmd_pages_show.go +++ b/mdl/executor/cmd_pages_show.go @@ -15,7 +15,7 @@ import ( func showPages(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -74,7 +74,7 @@ func showPages(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.excluded, r.folderPath, r.title, r.url, r.params}) } - return e.writeResult(result) + return writeResult(ctx, result) } func (e *Executor) showPages(moduleName string) error { diff --git a/mdl/executor/cmd_published_rest.go b/mdl/executor/cmd_published_rest.go index f0b57078..989128e9 100644 --- a/mdl/executor/cmd_published_rest.go +++ b/mdl/executor/cmd_published_rest.go @@ -23,7 +23,7 @@ func showPublishedRestServices(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list published REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -75,7 +75,7 @@ func showPublishedRestServices(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.path, r.version, r.resources, r.operations}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describePublishedRestService handles DESCRIBE PUBLISHED REST SERVICE command. @@ -87,7 +87,7 @@ func describePublishedRestService(ctx *ExecContext, name ast.QualifiedName) erro return mdlerrors.NewBackend("list published REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -168,7 +168,7 @@ func findPublishedRestService(ctx *ExecContext, moduleName, name string) (*model if err != nil { return nil, err } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, err } @@ -210,14 +210,14 @@ func execCreatePublishedRestService(ctx *ExecContext, s *ast.CreatePublishedRest } } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } containerID := module.ID if s.Folder != "" { - folderID, err := e.resolveFolder(module.ID, s.Folder) + folderID, err := resolveFolder(ctx, module.ID, s.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder '%s'", s.Folder), err) } @@ -272,7 +272,7 @@ func execDropPublishedRestService(ctx *ExecContext, s *ast.DropPublishedRestServ return mdlerrors.NewBackend("list published REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } diff --git a/mdl/executor/cmd_rename.go b/mdl/executor/cmd_rename.go index 8488c724..959aeaf6 100644 --- a/mdl/executor/cmd_rename.go +++ b/mdl/executor/cmd_rename.go @@ -45,7 +45,7 @@ func execRename(ctx *ExecContext, s *ast.RenameStmt) error { func execRenameEntity(ctx *ExecContext, s *ast.RenameStmt) error { e := ctx.executor // Find the entity - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -91,8 +91,8 @@ func execRenameEntity(ctx *ExecContext, s *ast.RenameStmt) error { return mdlerrors.NewBackend("update entity name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Renamed entity: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { @@ -107,7 +107,7 @@ func execRenameModule(ctx *ExecContext, s *ast.RenameStmt) error { oldModuleName := s.Name.Module newModuleName := s.NewName - module, err := e.findModule(oldModuleName) + module, err := findModule(ctx, oldModuleName) if err != nil { return err } @@ -139,8 +139,8 @@ func execRenameModule(ctx *ExecContext, s *ast.RenameStmt) error { return mdlerrors.NewBackend("update module name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Renamed module: %s → %s\n", oldModuleName, newModuleName) if len(allHits) > 0 { @@ -159,7 +159,7 @@ func execRenameDocument(ctx *ExecContext, s *ast.RenameStmt, docType string) err newQualifiedName := s.Name.Module + "." + s.NewName // Verify the document exists - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -228,7 +228,7 @@ func execRenameDocument(ctx *ExecContext, s *ast.RenameStmt, docType string) err return mdlerrors.NewBackend(fmt.Sprintf("rename %s", docType), err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Renamed %s: %s → %s\n", docType, oldQualifiedName, newQualifiedName) if len(hits) > 0 { @@ -248,7 +248,7 @@ func execRenameEnumeration(ctx *ExecContext, s *ast.RenameStmt) error { if err != nil { return mdlerrors.NewBackend("list enumerations", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -282,8 +282,8 @@ func execRenameEnumeration(ctx *ExecContext, s *ast.RenameStmt) error { // Also update enumeration refs in domain models (attribute types store qualified enum names) e.writer.UpdateEnumerationRefsInAllDomainModels(oldQualifiedName, newQualifiedName) - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Renamed enumeration: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { @@ -298,7 +298,7 @@ func execRenameAssociation(ctx *ExecContext, s *ast.RenameStmt) error { oldQualifiedName := s.Name.Module + "." + s.Name.Name newQualifiedName := s.Name.Module + "." + s.NewName - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -340,8 +340,8 @@ func execRenameAssociation(ctx *ExecContext, s *ast.RenameStmt) error { return mdlerrors.NewBackend("update association name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) fmt.Fprintf(ctx.Output, "Renamed association: %s → %s\n", oldQualifiedName, newQualifiedName) if len(hits) > 0 { diff --git a/mdl/executor/cmd_rest_clients.go b/mdl/executor/cmd_rest_clients.go index 576d4c2a..7c75ee9f 100644 --- a/mdl/executor/cmd_rest_clients.go +++ b/mdl/executor/cmd_rest_clients.go @@ -31,7 +31,7 @@ func showRestClients(ctx *ExecContext, moduleName string) error { return mdlerrors.NewBackend("list consumed REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -82,7 +82,7 @@ func showRestClients(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.module, r.qualifiedName, r.baseUrl, r.auth, r.ops}) } - return e.writeResult(result) + return writeResult(ctx, result) } // describeRestClient handles DESCRIBE REST CLIENT command. @@ -94,7 +94,7 @@ func describeRestClient(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewBackend("list consumed REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -296,14 +296,14 @@ func createRestClient(ctx *ExecContext, stmt *ast.CreateRestClientStmt) error { } moduleName := stmt.Name.Module - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return mdlerrors.NewNotFound("module", moduleName) } // Check for existing service with same name existingServices, _ := e.reader.ListConsumedRestServices() - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -326,7 +326,7 @@ func createRestClient(ctx *ExecContext, stmt *ast.CreateRestClientStmt) error { // Resolve folder if specified containerID := module.ID if stmt.Folder != "" { - folderID, err := e.resolveFolder(module.ID, stmt.Folder) + folderID, err := resolveFolder(ctx, module.ID, stmt.Folder) if err != nil { return mdlerrors.NewBackend(fmt.Sprintf("resolve folder '%s'", stmt.Folder), err) } @@ -474,7 +474,7 @@ func dropRestClient(ctx *ExecContext, stmt *ast.DropRestClientStmt) error { return mdlerrors.NewBackend("list consumed REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_search.go b/mdl/executor/cmd_search.go index fa6aef20..40fbf12c 100644 --- a/mdl/executor/cmd_search.go +++ b/mdl/executor/cmd_search.go @@ -13,13 +13,12 @@ import ( // execShowCallers handles SHOW CALLERS OF Module.Microflow [TRANSITIVE]. func execShowCallers(ctx *ExecContext, s *ast.ShowStmt) error { - e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW CALLERS") } // Ensure catalog is available with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -71,19 +70,18 @@ func execShowCallers(ctx *ExecContext, s *ast.ShowStmt) error { } fmt.Fprintf(ctx.Output, "Found %d caller(s)\n", result.Count) - e.outputCatalogResults(result) + outputCatalogResults(ctx, result) return nil } // execShowCallees handles SHOW CALLEES OF Module.Microflow [TRANSITIVE]. func execShowCallees(ctx *ExecContext, s *ast.ShowStmt) error { - e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW CALLEES") } // Ensure catalog is available with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -135,19 +133,18 @@ func execShowCallees(ctx *ExecContext, s *ast.ShowStmt) error { } fmt.Fprintf(ctx.Output, "Found %d callee(s)\n", result.Count) - e.outputCatalogResults(result) + outputCatalogResults(ctx, result) return nil } // execShowReferences handles SHOW REFERENCES TO Module.Entity. func execShowReferences(ctx *ExecContext, s *ast.ShowStmt) error { - e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW REFERENCES") } // Ensure catalog is available with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -173,20 +170,19 @@ func execShowReferences(ctx *ExecContext, s *ast.ShowStmt) error { } fmt.Fprintf(ctx.Output, "Found %d reference(s)\n", result.Count) - e.outputCatalogResults(result) + outputCatalogResults(ctx, result) return nil } // execShowImpact handles SHOW IMPACT OF Module.Entity. // This shows all elements that would be affected by changing the target. func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { - e := ctx.executor if s.Name == nil { return mdlerrors.NewValidation("target name required for SHOW IMPACT") } // Ensure catalog is available with full mode for refs - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return err } @@ -228,7 +224,7 @@ func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { fmt.Fprintln(ctx.Output) fmt.Fprintf(ctx.Output, "Found %d affected element(s)\n", result.Count) - e.outputCatalogResults(result) + outputCatalogResults(ctx, result) return nil } diff --git a/mdl/executor/cmd_security.go b/mdl/executor/cmd_security.go index 35794fce..e53d0e22 100644 --- a/mdl/executor/cmd_security.go +++ b/mdl/executor/cmd_security.go @@ -51,7 +51,7 @@ func showProjectSecurity(ctx *ExecContext) error { // showModuleRoles handles SHOW MODULE ROLES [IN module]. func showModuleRoles(ctx *ExecContext, moduleName string) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -80,7 +80,7 @@ func showModuleRoles(ctx *ExecContext, moduleName string) error { } result.Summary = fmt.Sprintf("(%d module roles)", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // showUserRoles handles SHOW USER ROLES. @@ -108,7 +108,7 @@ func showUserRoles(ctx *ExecContext) error { } result.Summary = fmt.Sprintf("(%d user roles)", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // showDemoUsers handles SHOW DEMO USERS. @@ -135,7 +135,7 @@ func showDemoUsers(ctx *ExecContext) error { } result.Summary = fmt.Sprintf("(%d demo users)", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // showAccessOnEntity handles SHOW ACCESS ON Module.Entity. @@ -145,7 +145,7 @@ func showAccessOnEntity(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewValidation("entity name required") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -257,7 +257,7 @@ func showAccessOnMicroflow(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewValidation("microflow name required") } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -292,7 +292,7 @@ func showAccessOnPage(ctx *ExecContext, name *ast.QualifiedName) error { return mdlerrors.NewValidation("page name required") } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -332,7 +332,7 @@ func showSecurityMatrix(ctx *ExecContext, moduleName string) error { return showSecurityMatrixJSON(ctx, moduleName) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -530,7 +530,7 @@ func showSecurityMatrix(ctx *ExecContext, moduleName string) error { // with one row per access rule across entities, microflows, pages, and workflows. func showSecurityMatrixJSON(ctx *ExecContext, moduleName string) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -640,13 +640,13 @@ func showSecurityMatrixJSON(ctx *ExecContext, moduleName string) error { }) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // describeModuleRole handles DESCRIBE MODULE ROLE Module.RoleName. func describeModuleRole(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_security_write.go b/mdl/executor/cmd_security_write.go index 27e8e260..f6409cfb 100644 --- a/mdl/executor/cmd_security_write.go +++ b/mdl/executor/cmd_security_write.go @@ -22,7 +22,7 @@ func execCreateModuleRole(ctx *ExecContext, s *ast.CreateModuleRoleStmt) error { return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -56,7 +56,7 @@ func execDropModuleRole(ctx *ExecContext, s *ast.DropModuleRoleStmt) error { return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -91,7 +91,7 @@ func execDropModuleRole(ctx *ExecContext, s *ast.DropModuleRoleStmt) error { } // Cascade: remove role from microflow/nanoflow/page allowed roles - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err == nil { // Microflows if mfs, err := e.reader.ListMicroflows(); err == nil { @@ -287,7 +287,7 @@ func execGrantEntityAccess(ctx *ExecContext, s *ast.GrantEntityAccessStmt) error return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Entity.Module) + module, err := findModule(ctx, s.Entity.Module) if err != nil { return err } @@ -444,7 +444,7 @@ func execRevokeEntityAccess(ctx *ExecContext, s *ast.RevokeEntityAccessStmt) err return mdlerrors.NewNotConnectedWrite() } - module, err := e.findModule(s.Entity.Module) + module, err := findModule(ctx, s.Entity.Module) if err != nil { return err } @@ -532,7 +532,7 @@ func execGrantMicroflowAccess(ctx *ExecContext, s *ast.GrantMicroflowAccessStmt) return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -595,7 +595,7 @@ func execRevokeMicroflowAccess(ctx *ExecContext, s *ast.RevokeMicroflowAccessStm return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -652,7 +652,7 @@ func execGrantPageAccess(ctx *ExecContext, s *ast.GrantPageAccessStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -715,7 +715,7 @@ func execRevokePageAccess(ctx *ExecContext, s *ast.RevokePageAccessStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -782,7 +782,7 @@ func execRevokeWorkflowAccess(ctx *ExecContext, s *ast.RevokeWorkflowAccessStmt) // validateModuleRole checks that a module role exists in the project. func validateModuleRole(ctx *ExecContext, role ast.QualifiedName) error { e := ctx.executor - module, err := e.findModule(role.Module) + module, err := findModule(ctx, role.Module) if err != nil { return fmt.Errorf("module not found for role %s.%s: %w", role.Module, role.Name, err) } @@ -1002,7 +1002,7 @@ func execGrantODataServiceAccess(ctx *ExecContext, s *ast.GrantODataServiceAcces return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1065,7 +1065,7 @@ func execRevokeODataServiceAccess(ctx *ExecContext, s *ast.RevokeODataServiceAcc return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1132,7 +1132,7 @@ func execGrantPublishedRestServiceAccess(ctx *ExecContext, s *ast.GrantPublished return err } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1194,7 +1194,7 @@ func execRevokePublishedRestServiceAccess(ctx *ExecContext, s *ast.RevokePublish return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1250,7 +1250,7 @@ func execUpdateSecurity(ctx *ExecContext, s *ast.UpdateSecurityStmt) error { return mdlerrors.NewNotConnectedWrite() } - modules, err := e.getModulesFromCache() + modules, err := getModulesFromCache(ctx) if err != nil { return err } diff --git a/mdl/executor/cmd_settings.go b/mdl/executor/cmd_settings.go index 1dca694f..5f5ce9bf 100644 --- a/mdl/executor/cmd_settings.go +++ b/mdl/executor/cmd_settings.go @@ -81,7 +81,7 @@ func showSettings(ctx *ExecContext) error { tr.Rows = append(tr.Rows, []any{"Web UI Settings", "OptimizedClient: " + ps.WebUI.UseOptimizedClient}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // Executor wrapper for unmigrated callers. diff --git a/mdl/executor/cmd_snippets.go b/mdl/executor/cmd_snippets.go index 7b5bc010..bd9cb995 100644 --- a/mdl/executor/cmd_snippets.go +++ b/mdl/executor/cmd_snippets.go @@ -16,7 +16,7 @@ import ( func showSnippets(ctx *ExecContext, moduleName string) error { e := ctx.executor // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -60,7 +60,7 @@ func showSnippets(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.module, r.name, r.folderPath, r.params}) } - return e.writeResult(result) + return writeResult(ctx, result) } func (e *Executor) showSnippets(moduleName string) error { diff --git a/mdl/executor/cmd_structure.go b/mdl/executor/cmd_structure.go index 7888fe9e..864f2d7d 100644 --- a/mdl/executor/cmd_structure.go +++ b/mdl/executor/cmd_structure.go @@ -27,7 +27,7 @@ func execShowStructure(ctx *ExecContext, s *ast.ShowStmt) error { depth := min(max(s.Depth, 1), 3) // Ensure catalog is built (fast mode is sufficient) - if err := e.ensureCatalog(false); err != nil { + if err := ensureCatalog(ctx, false); err != nil { return mdlerrors.NewBackend("build catalog", err) } @@ -71,7 +71,6 @@ func (e *Executor) execShowStructure(s *ast.ShowStmt) error { // structureDepth1JSON emits structure as a JSON table with one row per module // and columns for each element type count. func structureDepth1JSON(ctx *ExecContext, modules []structureModule) error { - e := ctx.executor entityCounts := queryCountByModule(ctx, "entities") mfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'MICROFLOW'") nfCounts := queryCountByModule(ctx, "microflows WHERE MicroflowType = 'NANOFLOW'") @@ -111,7 +110,7 @@ func structureDepth1JSON(ctx *ExecContext, modules []structureModule) error { beServiceCounts[m.Name], }) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // structureModule holds module info for structure output. @@ -284,7 +283,7 @@ func queryCountByModule(ctx *ExecContext, tableAndWhere string) map[string]int { func countByModuleFromReader(ctx *ExecContext, kind string) map[string]int { e := ctx.executor counts := make(map[string]int) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return counts } @@ -325,7 +324,7 @@ func pluralize(count int, singular, plural string) string { func structureDepth2(ctx *ExecContext, modules []structureModule) error { e := ctx.executor // Pre-load data that needs the reader - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -487,7 +486,7 @@ func structureDepth2(ctx *ExecContext, modules []structureModule) error { func structureDepth3(ctx *ExecContext, modules []structureModule) error { e := ctx.executor // Same data loading as depth 2 - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_styling.go b/mdl/executor/cmd_styling.go index 434cba99..5bd7047e 100644 --- a/mdl/executor/cmd_styling.go +++ b/mdl/executor/cmd_styling.go @@ -125,7 +125,7 @@ func execDescribeStyling(ctx *ExecContext, s *ast.DescribeStylingStmt) error { return mdlerrors.NewNotConnected() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -277,7 +277,7 @@ func execAlterStyling(ctx *ExecContext, s *ast.AlterStylingStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_widgets.go b/mdl/executor/cmd_widgets.go index 1c93473c..fc51829c 100644 --- a/mdl/executor/cmd_widgets.go +++ b/mdl/executor/cmd_widgets.go @@ -24,7 +24,7 @@ func execShowWidgets(ctx *ExecContext, s *ast.ShowWidgetsStmt) error { } // Ensure catalog is built (full mode for widgets) - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return mdlerrors.NewBackend("build catalog", err) } @@ -97,7 +97,7 @@ func execUpdateWidgets(ctx *ExecContext, s *ast.UpdateWidgetsStmt) error { } // Ensure catalog is built (full mode for widgets) - if err := e.ensureCatalog(true); err != nil { + if err := ensureCatalog(ctx, true); err != nil { return mdlerrors.NewBackend("build catalog", err) } diff --git a/mdl/executor/cmd_workflows.go b/mdl/executor/cmd_workflows.go index 8594115b..0d95a7bd 100644 --- a/mdl/executor/cmd_workflows.go +++ b/mdl/executor/cmd_workflows.go @@ -17,7 +17,7 @@ import ( // showWorkflows handles SHOW WORKFLOWS command. func showWorkflows(ctx *ExecContext, moduleName string) error { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -68,7 +68,7 @@ func showWorkflows(ctx *ExecContext, moduleName string) error { for _, r := range rows { result.Rows = append(result.Rows, []any{r.qualifiedName, r.activities, r.userTasks, r.decisions, r.paramEntity}) } - return e.writeResult(result) + return writeResult(ctx, result) } // Executor wrapper for unmigrated callers. @@ -155,7 +155,7 @@ func (e *Executor) describeWorkflow(name ast.QualifiedName) error { // describeWorkflowToString generates MDL-like output for a workflow and returns it as a string. func describeWorkflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { e := ctx.executor - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "", nil, mdlerrors.NewBackend("build hierarchy", err) } diff --git a/mdl/executor/cmd_workflows_write.go b/mdl/executor/cmd_workflows_write.go index 5c55a69a..034ea02e 100644 --- a/mdl/executor/cmd_workflows_write.go +++ b/mdl/executor/cmd_workflows_write.go @@ -23,13 +23,13 @@ func execCreateWorkflow(ctx *ExecContext, s *ast.CreateWorkflowStmt) error { return mdlerrors.NewNotConnectedWrite() } - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } // Check if workflow already exists - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -124,7 +124,7 @@ func execCreateWorkflow(ctx *ExecContext, s *ast.CreateWorkflowStmt) error { return mdlerrors.NewBackend("create workflow", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Created workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } @@ -141,7 +141,7 @@ func execDropWorkflow(ctx *ExecContext, s *ast.DropWorkflowStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -158,7 +158,7 @@ func execDropWorkflow(ctx *ExecContext, s *ast.DropWorkflowStmt) error { if err := e.writer.DeleteWorkflow(wf.ID); err != nil { return mdlerrors.NewBackend("delete workflow", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) fmt.Fprintf(ctx.Output, "Dropped workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } diff --git a/mdl/executor/executor_query.go b/mdl/executor/executor_query.go index 3d65b6c5..705d38a5 100644 --- a/mdl/executor/executor_query.go +++ b/mdl/executor/executor_query.go @@ -7,142 +7,144 @@ import ( mdlerrors "github.com/mendixlabs/mxcli/mdl/errors" ) -func (e *Executor) execShow(s *ast.ShowStmt) error { +func execShow(ctx *ExecContext, s *ast.ShowStmt) error { + e := ctx.executor if e.reader == nil && s.ObjectType != ast.ShowModules && s.ObjectType != ast.ShowFragments { return mdlerrors.NewNotConnected() } switch s.ObjectType { case ast.ShowModules: - return e.showModules() + return showModules(ctx) case ast.ShowEnumerations: - return e.showEnumerations(s.InModule) + return showEnumerations(ctx, s.InModule) case ast.ShowConstants: - return e.showConstants(s.InModule) + return showConstants(ctx, s.InModule) case ast.ShowConstantValues: - return e.showConstantValues(s.InModule) + return showConstantValues(ctx, s.InModule) case ast.ShowEntities: - return e.showEntities(s.InModule) + return showEntities(ctx, s.InModule) case ast.ShowEntity: - return e.showEntity(s.Name) + return showEntity(ctx, s.Name) case ast.ShowAssociations: - return e.showAssociations(s.InModule) + return showAssociations(ctx, s.InModule) case ast.ShowAssociation: - return e.showAssociation(s.Name) + return showAssociation(ctx, s.Name) case ast.ShowMicroflows: - return e.showMicroflows(s.InModule) + return showMicroflows(ctx, s.InModule) case ast.ShowNanoflows: - return e.showNanoflows(s.InModule) + return showNanoflows(ctx, s.InModule) case ast.ShowPages: - return e.showPages(s.InModule) + return showPages(ctx, s.InModule) case ast.ShowSnippets: - return e.showSnippets(s.InModule) + return showSnippets(ctx, s.InModule) case ast.ShowLayouts: - return e.showLayouts(s.InModule) + return showLayouts(ctx, s.InModule) case ast.ShowJavaActions: - return e.showJavaActions(s.InModule) + return showJavaActions(ctx, s.InModule) case ast.ShowJavaScriptActions: - return e.showJavaScriptActions(s.InModule) + return showJavaScriptActions(ctx, s.InModule) case ast.ShowVersion: - return e.showVersion() + return showVersion(ctx) case ast.ShowCatalogTables: - return e.execShowCatalogTables() + return execShowCatalogTables(ctx) case ast.ShowCatalogStatus: - return e.execShowCatalogStatus() + return execShowCatalogStatus(ctx) case ast.ShowCallers: - return e.execShowCallers(s) + return execShowCallers(ctx, s) case ast.ShowCallees: - return e.execShowCallees(s) + return execShowCallees(ctx, s) case ast.ShowReferences: - return e.execShowReferences(s) + return execShowReferences(ctx, s) case ast.ShowImpact: - return e.execShowImpact(s) + return execShowImpact(ctx, s) case ast.ShowContext: - return e.execShowContext(s) + return execShowContext(ctx, s) case ast.ShowProjectSecurity: - return e.showProjectSecurity() + return showProjectSecurity(ctx) case ast.ShowModuleRoles: - return e.showModuleRoles(s.InModule) + return showModuleRoles(ctx, s.InModule) case ast.ShowUserRoles: - return e.showUserRoles() + return showUserRoles(ctx) case ast.ShowDemoUsers: - return e.showDemoUsers() + return showDemoUsers(ctx) case ast.ShowAccessOn: - return e.showAccessOnEntity(s.Name) + return showAccessOnEntity(ctx, s.Name) case ast.ShowAccessOnMicroflow: - return e.showAccessOnMicroflow(s.Name) + return showAccessOnMicroflow(ctx, s.Name) case ast.ShowAccessOnPage: - return e.showAccessOnPage(s.Name) + return showAccessOnPage(ctx, s.Name) case ast.ShowAccessOnWorkflow: - return e.showAccessOnWorkflow(s.Name) + return showAccessOnWorkflow(ctx, s.Name) case ast.ShowSecurityMatrix: - return e.showSecurityMatrix(s.InModule) + return showSecurityMatrix(ctx, s.InModule) case ast.ShowODataClients: - return e.showODataClients(s.InModule) + return showODataClients(ctx, s.InModule) case ast.ShowODataServices: - return e.showODataServices(s.InModule) + return showODataServices(ctx, s.InModule) case ast.ShowExternalEntities: - return e.showExternalEntities(s.InModule) + return showExternalEntities(ctx, s.InModule) case ast.ShowExternalActions: - return e.showExternalActions(s.InModule) + return showExternalActions(ctx, s.InModule) case ast.ShowNavigation: - return e.showNavigation() + return showNavigation(ctx) case ast.ShowNavigationMenu: - return e.showNavigationMenu(s.Name) + return showNavigationMenu(ctx, s.Name) case ast.ShowNavigationHomes: - return e.showNavigationHomes() + return showNavigationHomes(ctx) case ast.ShowStructure: - return e.execShowStructure(s) + return execShowStructure(ctx, s) case ast.ShowWorkflows: - return e.showWorkflows(s.InModule) + return showWorkflows(ctx, s.InModule) case ast.ShowBusinessEventServices: - return e.showBusinessEventServices(s.InModule) + return showBusinessEventServices(ctx, s.InModule) case ast.ShowBusinessEventClients: - return e.showBusinessEventClients(s.InModule) + return showBusinessEventClients(ctx, s.InModule) case ast.ShowBusinessEvents: - return e.showBusinessEvents(s.InModule) + return showBusinessEvents(ctx, s.InModule) case ast.ShowSettings: - return e.showSettings() + return showSettings(ctx) case ast.ShowFragments: - return e.showFragments() + return showFragments(ctx) case ast.ShowDatabaseConnections: - return e.showDatabaseConnections(s.InModule) + return showDatabaseConnections(ctx, s.InModule) case ast.ShowImageCollections: - return e.showImageCollections(s.InModule) + return showImageCollections(ctx, s.InModule) case ast.ShowModels: - return e.showAgentEditorModels(s.InModule) + return showAgentEditorModels(ctx, s.InModule) case ast.ShowAgents: - return e.showAgentEditorAgents(s.InModule) + return showAgentEditorAgents(ctx, s.InModule) case ast.ShowKnowledgeBases: - return e.showAgentEditorKnowledgeBases(s.InModule) + return showAgentEditorKnowledgeBases(ctx, s.InModule) case ast.ShowConsumedMCPServices: - return e.showAgentEditorConsumedMCPServices(s.InModule) + return showAgentEditorConsumedMCPServices(ctx, s.InModule) case ast.ShowRestClients: - return e.showRestClients(s.InModule) + return showRestClients(ctx, s.InModule) case ast.ShowPublishedRestServices: - return e.showPublishedRestServices(s.InModule) + return showPublishedRestServices(ctx, s.InModule) case ast.ShowDataTransformers: - return e.listDataTransformers(s.InModule) + return listDataTransformers(ctx, s.InModule) case ast.ShowContractEntities: - return e.showContractEntities(s.Name) + return showContractEntities(ctx, s.Name) case ast.ShowContractActions: - return e.showContractActions(s.Name) + return showContractActions(ctx, s.Name) case ast.ShowContractChannels: - return e.showContractChannels(s.Name) + return showContractChannels(ctx, s.Name) case ast.ShowContractMessages: - return e.showContractMessages(s.Name) + return showContractMessages(ctx, s.Name) case ast.ShowJsonStructures: - return e.showJsonStructures(s.InModule) + return showJsonStructures(ctx, s.InModule) case ast.ShowImportMappings: - return e.showImportMappings(s.InModule) + return showImportMappings(ctx, s.InModule) case ast.ShowExportMappings: - return e.showExportMappings(s.InModule) + return showExportMappings(ctx, s.InModule) default: return mdlerrors.NewUnsupported("unknown show object type") } } -func (e *Executor) execDescribe(s *ast.DescribeStmt) error { +func execDescribe(ctx *ExecContext, s *ast.DescribeStmt) error { + e := ctx.executor if e.reader == nil && s.ObjectType != ast.DescribeFragment { return mdlerrors.NewNotConnected() } @@ -151,84 +153,84 @@ func (e *Executor) execDescribe(s *ast.DescribeStmt) error { objectType := describeObjectTypeLabel(s.ObjectType) name := s.Name.String() - return e.writeDescribeJSON(name, objectType, func() error { + return writeDescribeJSON(ctx, name, objectType, func() error { switch s.ObjectType { case ast.DescribeEnumeration: - return e.describeEnumeration(s.Name) + return describeEnumeration(ctx, s.Name) case ast.DescribeEntity: - return e.describeEntity(s.Name) + return describeEntity(ctx, s.Name) case ast.DescribeAssociation: - return e.describeAssociation(s.Name) + return describeAssociation(ctx, s.Name) case ast.DescribeMicroflow: - return e.describeMicroflow(s.Name) + return describeMicroflow(ctx, s.Name) case ast.DescribeNanoflow: - return e.describeNanoflow(s.Name) + return describeNanoflow(ctx, s.Name) case ast.DescribeModule: - return e.describeModule(s.Name.Module, s.WithAll) + return describeModule(ctx, s.Name.Module, s.WithAll) case ast.DescribePage: - return e.describePage(s.Name) + return describePage(ctx, s.Name) case ast.DescribeSnippet: - return e.describeSnippet(s.Name) + return describeSnippet(ctx, s.Name) case ast.DescribeLayout: - return e.describeLayout(s.Name) + return describeLayout(ctx, s.Name) case ast.DescribeConstant: - return e.describeConstant(s.Name) + return describeConstant(ctx, s.Name) case ast.DescribeJavaAction: - return e.describeJavaAction(s.Name) + return describeJavaAction(ctx, s.Name) case ast.DescribeJavaScriptAction: - return e.describeJavaScriptAction(s.Name) + return describeJavaScriptAction(ctx, s.Name) case ast.DescribeModuleRole: - return e.describeModuleRole(s.Name) + return describeModuleRole(ctx, s.Name) case ast.DescribeUserRole: - return e.describeUserRole(s.Name) + return describeUserRole(ctx, s.Name) case ast.DescribeDemoUser: - return e.describeDemoUser(s.Name.Name) + return describeDemoUser(ctx, s.Name.Name) case ast.DescribeODataClient: - return e.describeODataClient(s.Name) + return describeODataClient(ctx, s.Name) case ast.DescribeODataService: - return e.describeODataService(s.Name) + return describeODataService(ctx, s.Name) case ast.DescribeExternalEntity: - return e.describeExternalEntity(s.Name) + return describeExternalEntity(ctx, s.Name) case ast.DescribeNavigation: - return e.describeNavigation(s.Name) + return describeNavigation(ctx, s.Name) case ast.DescribeWorkflow: - return e.describeWorkflow(s.Name) + return describeWorkflow(ctx, s.Name) case ast.DescribeBusinessEventService: - return e.describeBusinessEventService(s.Name) + return describeBusinessEventService(ctx, s.Name) case ast.DescribeDatabaseConnection: - return e.describeDatabaseConnection(s.Name) + return describeDatabaseConnection(ctx, s.Name) case ast.DescribeSettings: - return e.describeSettings() + return describeSettings(ctx) case ast.DescribeFragment: - return e.describeFragment(s.Name) + return describeFragment(ctx, s.Name) case ast.DescribeImageCollection: - return e.describeImageCollection(s.Name) + return describeImageCollection(ctx, s.Name) case ast.DescribeModel: - return e.describeAgentEditorModel(s.Name) + return describeAgentEditorModel(ctx, s.Name) case ast.DescribeAgent: - return e.describeAgentEditorAgent(s.Name) + return describeAgentEditorAgent(ctx, s.Name) case ast.DescribeKnowledgeBase: - return e.describeAgentEditorKnowledgeBase(s.Name) + return describeAgentEditorKnowledgeBase(ctx, s.Name) case ast.DescribeConsumedMCPService: - return e.describeAgentEditorConsumedMCPService(s.Name) + return describeAgentEditorConsumedMCPService(ctx, s.Name) case ast.DescribeRestClient: - return e.describeRestClient(s.Name) + return describeRestClient(ctx, s.Name) case ast.DescribePublishedRestService: - return e.describePublishedRestService(s.Name) + return describePublishedRestService(ctx, s.Name) case ast.DescribeDataTransformer: - return e.describeDataTransformer(s.Name) + return describeDataTransformer(ctx, s.Name) case ast.DescribeContractEntity: - return e.describeContractEntity(s.Name, s.Format) + return describeContractEntity(ctx, s.Name, s.Format) case ast.DescribeContractAction: - return e.describeContractAction(s.Name, s.Format) + return describeContractAction(ctx, s.Name, s.Format) case ast.DescribeContractMessage: - return e.describeContractMessage(s.Name) + return describeContractMessage(ctx, s.Name) case ast.DescribeJsonStructure: - return e.describeJsonStructure(s.Name) + return describeJsonStructure(ctx, s.Name) case ast.DescribeImportMapping: - return e.describeImportMapping(s.Name) + return describeImportMapping(ctx, s.Name) case ast.DescribeExportMapping: - return e.describeExportMapping(s.Name) + return describeExportMapping(ctx, s.Name) default: return mdlerrors.NewUnsupported("unknown describe object type") } diff --git a/mdl/executor/format.go b/mdl/executor/format.go index 6a2c8922..a9fbb9f5 100644 --- a/mdl/executor/format.go +++ b/mdl/executor/format.go @@ -4,6 +4,7 @@ package executor import ( "bytes" + "context" "encoding/json" "fmt" "strings" @@ -26,17 +27,17 @@ type TableResult struct { Summary string // optional summary line, e.g. "(42 entities)" } -// writeResult renders a TableResult to e.output in the current format. -func (e *Executor) writeResult(r *TableResult) error { - if e.format == FormatJSON { - return e.writeResultJSON(r) +// writeResult renders a TableResult to ctx.Output in the current format. +func writeResult(ctx *ExecContext, r *TableResult) error { + if ctx.Format == FormatJSON { + return writeResultJSON(ctx, r) } - e.writeResultTable(r) + writeResultTable(ctx, r) return nil } // writeResultTable renders a TableResult as a pipe-delimited markdown table. -func (e *Executor) writeResultTable(r *TableResult) { +func writeResultTable(ctx *ExecContext, r *TableResult) { if len(r.Columns) == 0 { return } @@ -59,40 +60,40 @@ func (e *Executor) writeResultTable(r *TableResult) { } // Print header. - fmt.Fprint(e.output, "|") + fmt.Fprint(ctx.Output, "|") for i, col := range r.Columns { - fmt.Fprintf(e.output, " %-*s |", widths[i], col) + fmt.Fprintf(ctx.Output, " %-*s |", widths[i], col) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Print separator. - fmt.Fprint(e.output, "|") + fmt.Fprint(ctx.Output, "|") for _, w := range widths { - fmt.Fprintf(e.output, "-%s-|", strings.Repeat("-", w)) + fmt.Fprintf(ctx.Output, "-%s-|", strings.Repeat("-", w)) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) // Print rows. for _, row := range r.Rows { - fmt.Fprint(e.output, "|") + fmt.Fprint(ctx.Output, "|") for i := range r.Columns { var s string if i < len(row) { s = formatCellValue(row[i]) } - fmt.Fprintf(e.output, " %-*s |", widths[i], s) + fmt.Fprintf(ctx.Output, " %-*s |", widths[i], s) } - fmt.Fprintln(e.output) + fmt.Fprintln(ctx.Output) } // Print summary. if r.Summary != "" { - fmt.Fprintf(e.output, "\n%s\n", r.Summary) + fmt.Fprintf(ctx.Output, "\n%s\n", r.Summary) } } // writeResultJSON renders a TableResult as a JSON array of objects. -func (e *Executor) writeResultJSON(r *TableResult) error { +func writeResultJSON(ctx *ExecContext, r *TableResult) error { objects := make([]map[string]any, 0, len(r.Rows)) for _, row := range r.Rows { obj := make(map[string]any, len(r.Columns)) @@ -104,7 +105,7 @@ func (e *Executor) writeResultJSON(r *TableResult) error { objects = append(objects, obj) } - enc := json.NewEncoder(e.output) + enc := json.NewEncoder(ctx.Output) enc.SetIndent("", " ") return enc.Encode(objects) } @@ -112,19 +113,23 @@ func (e *Executor) writeResultJSON(r *TableResult) error { // writeDescribeJSON wraps a describe handler's output in a JSON envelope. // In table/text mode it calls fn directly. In JSON mode it captures fn's output // and wraps it as {"name": ..., "type": ..., "mdl": ...}. -func (e *Executor) writeDescribeJSON(name, objectType string, fn func() error) error { - if e.format != FormatJSON { +func writeDescribeJSON(ctx *ExecContext, name, objectType string, fn func() error) error { + e := ctx.executor + if ctx.Format != FormatJSON { return fn() } // Capture the text output from fn. var buf bytes.Buffer - origOutput := e.output + origOutput := ctx.Output + origEOutput := e.output origGuard := e.guard + ctx.Output = &buf e.output = &buf e.guard = nil // disable line guard for capture err := fn() - e.output = origOutput + ctx.Output = origOutput + e.output = origEOutput e.guard = origGuard if err != nil { return err @@ -135,11 +140,31 @@ func (e *Executor) writeDescribeJSON(name, objectType string, fn func() error) e "type": objectType, "mdl": buf.String(), } - enc := json.NewEncoder(e.output) + enc := json.NewEncoder(ctx.Output) enc.SetIndent("", " ") return enc.Encode(result) } +// ---------------------------------------------------------------------------- +// Executor method wrappers (for callers in unmigrated files) +// ---------------------------------------------------------------------------- + +func (e *Executor) writeResult(r *TableResult) error { + return writeResult(e.newExecContext(context.Background()), r) +} + +func (e *Executor) writeResultTable(r *TableResult) { + writeResultTable(e.newExecContext(context.Background()), r) +} + +func (e *Executor) writeResultJSON(r *TableResult) error { + return writeResultJSON(e.newExecContext(context.Background()), r) +} + +func (e *Executor) writeDescribeJSON(name, objectType string, fn func() error) error { + return writeDescribeJSON(e.newExecContext(context.Background()), name, objectType, fn) +} + // formatCellValue formats a value for table cell display. func formatCellValue(val any) string { if val == nil { diff --git a/mdl/executor/helpers.go b/mdl/executor/helpers.go index 3d97e80e..25b5040e 100644 --- a/mdl/executor/helpers.go +++ b/mdl/executor/helpers.go @@ -5,6 +5,7 @@ package executor import ( + "context" "fmt" "strings" @@ -20,36 +21,37 @@ import ( // ---------------------------------------------------------------------------- // getModulesFromCache returns cached modules or loads them. -func (e *Executor) getModulesFromCache() ([]*model.Module, error) { - if e.cache != nil && e.cache.modules != nil { - return e.cache.modules, nil +func getModulesFromCache(ctx *ExecContext) ([]*model.Module, error) { + e := ctx.executor + if ctx.Cache != nil && ctx.Cache.modules != nil { + return ctx.Cache.modules, nil } modules, err := e.reader.ListModules() if err != nil { return nil, err } - if e.cache != nil { - e.cache.modules = modules + if ctx.Cache != nil { + ctx.Cache.modules = modules } return modules, nil } // invalidateModuleCache clears the module cache so next lookup gets fresh data. // Also invalidates the hierarchy cache since new modules affect hierarchy. -func (e *Executor) invalidateModuleCache() { - if e.cache != nil { - e.cache.modules = nil - e.cache.hierarchy = nil +func invalidateModuleCache(ctx *ExecContext) { + if ctx.Cache != nil { + ctx.Cache.modules = nil + ctx.Cache.hierarchy = nil } } -func (e *Executor) findModule(name string) (*model.Module, error) { +func findModule(ctx *ExecContext, name string) (*model.Module, error) { // Module name is required - objects must always belong to a module if name == "" { return nil, mdlerrors.NewValidation("module name is required: objects must be created within a module (use ModuleName.ObjectName syntax)") } - modules, err := e.getModulesFromCache() + modules, err := getModulesFromCache(ctx) if err != nil { return nil, mdlerrors.NewBackend("list modules", err) } @@ -66,8 +68,9 @@ func (e *Executor) findModule(name string) (*model.Module, error) { // findOrCreateModule looks up a module by name, auto-creating it if it doesn't exist // and the executor has write access. Used by CREATE operations to avoid requiring // manual module creation. -func (e *Executor) findOrCreateModule(name string) (*model.Module, error) { - m, err := e.findModule(name) +func findOrCreateModule(ctx *ExecContext, name string) (*model.Module, error) { + e := ctx.executor + m, err := findModule(ctx, name) if err == nil { return m, nil } @@ -78,11 +81,11 @@ func (e *Executor) findOrCreateModule(name string) (*model.Module, error) { if createErr := e.execCreateModule(&ast.CreateModuleStmt{Name: name}); createErr != nil { return nil, mdlerrors.NewBackend("auto-create module "+name, createErr) } - return e.findModule(name) + return findModule(ctx, name) } -func (e *Executor) findModuleByID(id model.ID) (*model.Module, error) { - modules, err := e.getModulesFromCache() +func findModuleByID(ctx *ExecContext, id model.ID) (*model.Module, error) { + modules, err := getModulesFromCache(ctx) if err != nil { return nil, mdlerrors.NewBackend("list modules", err) } @@ -98,7 +101,8 @@ func (e *Executor) findModuleByID(id model.ID) (*model.Module, error) { // resolveFolder resolves a folder path (e.g., "Resources/Images") to a folder ID. // The path is relative to the given module. If the folder doesn't exist, it creates it. -func (e *Executor) resolveFolder(moduleID model.ID, folderPath string) (model.ID, error) { +func resolveFolder(ctx *ExecContext, moduleID model.ID, folderPath string) (model.ID, error) { + e := ctx.executor if folderPath == "" { return moduleID, nil } @@ -131,7 +135,7 @@ func (e *Executor) resolveFolder(moduleID model.ID, folderPath string) (model.ID } else { // Create the folder parentID := currentContainerID - newFolderID, err := e.createFolder(part, parentID) + newFolderID, err := createFolder(ctx, part, parentID) if err != nil { return "", mdlerrors.NewBackend("create folder "+part, err) } @@ -150,7 +154,8 @@ func (e *Executor) resolveFolder(moduleID model.ID, folderPath string) (model.ID } // createFolder creates a new folder in the project. -func (e *Executor) createFolder(name string, containerID model.ID) (model.ID, error) { +func createFolder(ctx *ExecContext, name string, containerID model.ID) (model.ID, error) { + e := ctx.executor folder := &model.Folder{ BaseElement: model.BaseElement{ ID: model.ID(mpr.GenerateID()), @@ -172,7 +177,8 @@ func (e *Executor) createFolder(name string, containerID model.ID) (model.ID, er // ---------------------------------------------------------------------------- // enumerationExists checks if an enumeration exists in the project. -func (e *Executor) enumerationExists(qualifiedName string) bool { +func enumerationExists(ctx *ExecContext, qualifiedName string) bool { + e := ctx.executor if e.reader == nil { return false } @@ -185,7 +191,7 @@ func (e *Executor) enumerationExists(qualifiedName string) bool { moduleName, enumName := parts[0], parts[1] // Find the module to get its ID - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return false } @@ -211,7 +217,8 @@ func (e *Executor) enumerationExists(qualifiedName string) bool { // validateWidgetReferences validates all qualified name references in a widget tree. // It checks DataSource (microflow/nanoflow/entity), Action (page/microflow/nanoflow), // and Snippet references. -func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptContext) []string { +func validateWidgetReferences(ctx *ExecContext, widgets []*ast.WidgetV3, sc *scriptContext) []string { + e := ctx.executor if e.reader == nil || len(widgets) == 0 { return nil } @@ -228,7 +235,7 @@ func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptC var errors []string if len(refs.microflows) > 0 { - known := e.buildMicroflowQualifiedNames() + known := buildMicroflowQualifiedNames(ctx) for _, ref := range refs.microflows { if !known[ref] && !sc.microflows[ref] { errors = append(errors, fmt.Sprintf("microflow not found: %s", ref)) @@ -237,7 +244,7 @@ func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptC } if len(refs.nanoflows) > 0 { - known := e.buildNanoflowQualifiedNames() + known := buildNanoflowQualifiedNames(ctx) for _, ref := range refs.nanoflows { if !known[ref] { errors = append(errors, fmt.Sprintf("nanoflow not found: %s", ref)) @@ -246,7 +253,7 @@ func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptC } if len(refs.pages) > 0 { - known := e.buildPageQualifiedNames() + known := buildPageQualifiedNames(ctx) for _, ref := range refs.pages { if !known[ref] && !sc.pages[ref] { errors = append(errors, fmt.Sprintf("page not found: %s", ref)) @@ -255,7 +262,7 @@ func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptC } if len(refs.snippets) > 0 { - known := e.buildSnippetQualifiedNames() + known := buildSnippetQualifiedNames(ctx) for _, ref := range refs.snippets { if !known[ref] && !sc.snippets[ref] { errors = append(errors, fmt.Sprintf("snippet not found: %s", ref)) @@ -264,7 +271,7 @@ func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptC } if len(refs.entities) > 0 { - known := e.buildEntityQualifiedNames() + known := buildEntityQualifiedNames(ctx) for _, ref := range refs.entities { if !known[ref] && !sc.entities[ref] { errors = append(errors, fmt.Sprintf("entity not found: %s", ref)) @@ -358,9 +365,10 @@ func (c *widgetRefCollector) collectFromAction(action *ast.ActionV3) { // ---------------------------------------------------------------------------- // buildMicroflowQualifiedNames returns a set of all microflow qualified names in the project. -func (e *Executor) buildMicroflowQualifiedNames() map[string]bool { +func buildMicroflowQualifiedNames(ctx *ExecContext) map[string]bool { + e := ctx.executor result := make(map[string]bool) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return result } @@ -376,9 +384,10 @@ func (e *Executor) buildMicroflowQualifiedNames() map[string]bool { } // buildNanoflowQualifiedNames returns a set of all nanoflow qualified names in the project. -func (e *Executor) buildNanoflowQualifiedNames() map[string]bool { +func buildNanoflowQualifiedNames(ctx *ExecContext) map[string]bool { + e := ctx.executor result := make(map[string]bool) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return result } @@ -394,9 +403,10 @@ func (e *Executor) buildNanoflowQualifiedNames() map[string]bool { } // buildPageQualifiedNames returns a set of all page qualified names in the project. -func (e *Executor) buildPageQualifiedNames() map[string]bool { +func buildPageQualifiedNames(ctx *ExecContext) map[string]bool { + e := ctx.executor result := make(map[string]bool) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return result } @@ -412,9 +422,10 @@ func (e *Executor) buildPageQualifiedNames() map[string]bool { } // buildSnippetQualifiedNames returns a set of all snippet qualified names in the project. -func (e *Executor) buildSnippetQualifiedNames() map[string]bool { +func buildSnippetQualifiedNames(ctx *ExecContext) map[string]bool { + e := ctx.executor result := make(map[string]bool) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return result } @@ -430,12 +441,13 @@ func (e *Executor) buildSnippetQualifiedNames() map[string]bool { } // buildEntityQualifiedNames returns a set of all entity qualified names in the project. -func (e *Executor) buildEntityQualifiedNames() map[string]bool { +func buildEntityQualifiedNames(ctx *ExecContext) map[string]bool { result := make(map[string]bool) - modules, err := e.getModulesFromCache() + modules, err := getModulesFromCache(ctx) if err != nil { return result } + e := ctx.executor moduleNames := make(map[model.ID]string) for _, m := range modules { moduleNames[m.ID] = m.Name @@ -457,9 +469,10 @@ func (e *Executor) buildEntityQualifiedNames() map[string]bool { } // buildJavaActionQualifiedNames returns a set of all java action qualified names in the project. -func (e *Executor) buildJavaActionQualifiedNames() map[string]bool { +func buildJavaActionQualifiedNames(ctx *ExecContext) map[string]bool { + e := ctx.executor result := make(map[string]bool) - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return result } @@ -474,6 +487,70 @@ func (e *Executor) buildJavaActionQualifiedNames() map[string]bool { return result } +// ---------------------------------------------------------------------------- +// Executor method wrappers (for callers in unmigrated files) +// ---------------------------------------------------------------------------- + +func (e *Executor) getModulesFromCache() ([]*model.Module, error) { + return getModulesFromCache(e.newExecContext(context.Background())) +} + +func (e *Executor) invalidateModuleCache() { + invalidateModuleCache(e.newExecContext(context.Background())) +} + +func (e *Executor) findModule(name string) (*model.Module, error) { + return findModule(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findOrCreateModule(name string) (*model.Module, error) { + return findOrCreateModule(e.newExecContext(context.Background()), name) +} + +func (e *Executor) findModuleByID(id model.ID) (*model.Module, error) { + return findModuleByID(e.newExecContext(context.Background()), id) +} + +func (e *Executor) resolveFolder(moduleID model.ID, folderPath string) (model.ID, error) { + return resolveFolder(e.newExecContext(context.Background()), moduleID, folderPath) +} + +func (e *Executor) createFolder(name string, containerID model.ID) (model.ID, error) { + return createFolder(e.newExecContext(context.Background()), name, containerID) +} + +func (e *Executor) enumerationExists(qualifiedName string) bool { + return enumerationExists(e.newExecContext(context.Background()), qualifiedName) +} + +func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptContext) []string { + return validateWidgetReferences(e.newExecContext(context.Background()), widgets, sc) +} + +func (e *Executor) buildMicroflowQualifiedNames() map[string]bool { + return buildMicroflowQualifiedNames(e.newExecContext(context.Background())) +} + +func (e *Executor) buildNanoflowQualifiedNames() map[string]bool { + return buildNanoflowQualifiedNames(e.newExecContext(context.Background())) +} + +func (e *Executor) buildPageQualifiedNames() map[string]bool { + return buildPageQualifiedNames(e.newExecContext(context.Background())) +} + +func (e *Executor) buildSnippetQualifiedNames() map[string]bool { + return buildSnippetQualifiedNames(e.newExecContext(context.Background())) +} + +func (e *Executor) buildEntityQualifiedNames() map[string]bool { + return buildEntityQualifiedNames(e.newExecContext(context.Background())) +} + +func (e *Executor) buildJavaActionQualifiedNames() map[string]bool { + return buildJavaActionQualifiedNames(e.newExecContext(context.Background())) +} + // ---------------------------------------------------------------------------- // Data Type Conversion // ---------------------------------------------------------------------------- diff --git a/mdl/executor/hierarchy.go b/mdl/executor/hierarchy.go index c8101f02..2906864f 100644 --- a/mdl/executor/hierarchy.go +++ b/mdl/executor/hierarchy.go @@ -3,6 +3,7 @@ package executor import ( + "context" "strings" "github.com/mendixlabs/mxcli/model" @@ -110,37 +111,55 @@ func (h *ContainerHierarchy) GetQualifiedName(containerID model.ID, name string) } // getHierarchy returns a cached ContainerHierarchy or creates a new one. -func (e *Executor) getHierarchy() (*ContainerHierarchy, error) { +func getHierarchy(ctx *ExecContext) (*ContainerHierarchy, error) { + e := ctx.executor // Ensure cache exists if e.reader == nil { return nil, nil } - if e.cache == nil { - e.cache = &executorCache{} + if ctx.Cache == nil { + ctx.Cache = &executorCache{} + e.cache = ctx.Cache } - if e.cache.hierarchy != nil { - return e.cache.hierarchy, nil + if ctx.Cache.hierarchy != nil { + return ctx.Cache.hierarchy, nil } h, err := NewContainerHierarchy(e.reader) if err != nil { return nil, err } - e.cache.hierarchy = h + ctx.Cache.hierarchy = h return h, nil } // invalidateHierarchy clears the cached hierarchy so it will be rebuilt on next access. // This should be called after any write operation that creates or deletes units. -func (e *Executor) invalidateHierarchy() { - if e.cache != nil { - e.cache.hierarchy = nil +func invalidateHierarchy(ctx *ExecContext) { + if ctx.Cache != nil { + ctx.Cache.hierarchy = nil } } // invalidateDomainModelsCache clears the cached domain models so they will be reloaded. // This should be called after any write operation that creates or modifies entities. -func (e *Executor) invalidateDomainModelsCache() { - if e.cache != nil { - e.cache.domainModels = nil +func invalidateDomainModelsCache(ctx *ExecContext) { + if ctx.Cache != nil { + ctx.Cache.domainModels = nil } } + +// ---------------------------------------------------------------------------- +// Executor method wrappers (for callers in unmigrated files) +// ---------------------------------------------------------------------------- + +func (e *Executor) getHierarchy() (*ContainerHierarchy, error) { + return getHierarchy(e.newExecContext(context.Background())) +} + +func (e *Executor) invalidateHierarchy() { + invalidateHierarchy(e.newExecContext(context.Background())) +} + +func (e *Executor) invalidateDomainModelsCache() { + invalidateDomainModelsCache(e.newExecContext(context.Background())) +} diff --git a/mdl/executor/oql_type_inference.go b/mdl/executor/oql_type_inference.go index c314262c..d7da8474 100644 --- a/mdl/executor/oql_type_inference.go +++ b/mdl/executor/oql_type_inference.go @@ -3,6 +3,7 @@ package executor import ( + "context" "fmt" "regexp" "strings" @@ -24,8 +25,8 @@ type OQLColumnInfo struct { AggregateFunc string // The aggregate function name (COUNT, SUM, AVG, etc.) } -// InferOQLTypes analyzes an OQL query and returns the expected types for each column. -func (e *Executor) InferOQLTypes(oqlQuery string, declaredAttrs []ast.ViewAttribute) ([]OQLColumnInfo, []string) { +// inferOQLTypes analyzes an OQL query and returns the expected types for each column. +func inferOQLTypes(ctx *ExecContext, oqlQuery string, declaredAttrs []ast.ViewAttribute) ([]OQLColumnInfo, []string) { var warnings []string var columns []OQLColumnInfo @@ -64,7 +65,7 @@ func (e *Executor) InferOQLTypes(oqlQuery string, declaredAttrs []ast.ViewAttrib } // Infer type from expression - col.InferredType = e.inferTypeFromExpression(expr, &col, aliasMap) + col.InferredType = inferTypeFromExpression(ctx, expr, &col, aliasMap) columns = append(columns, col) } @@ -72,6 +73,11 @@ func (e *Executor) InferOQLTypes(oqlQuery string, declaredAttrs []ast.ViewAttrib return columns, warnings } +// InferOQLTypes analyzes an OQL query and returns the expected types for each column. +func (e *Executor) InferOQLTypes(oqlQuery string, declaredAttrs []ast.ViewAttribute) ([]OQLColumnInfo, []string) { + return inferOQLTypes(e.newExecContext(context.Background()), oqlQuery, declaredAttrs) +} + // extractAliasMap parses the FROM clause to build a map of alias -> qualified entity name. func extractAliasMap(oql string) map[string]string { aliasMap := make(map[string]string) @@ -279,8 +285,8 @@ func inferCaseType(expr string) ast.DataType { return ast.DataType{Kind: ast.TypeUnknown} } -// ValidateViewEntityTypes validates that declared attribute types match inferred OQL types. -func (e *Executor) ValidateViewEntityTypes(stmt *ast.CreateViewEntityStmt) []string { +// validateViewEntityTypes validates that declared attribute types match inferred OQL types. +func validateViewEntityTypes(ctx *ExecContext, stmt *ast.CreateViewEntityStmt) []string { var errors []string // First validate OQL syntax for common mistakes @@ -295,7 +301,7 @@ func (e *Executor) ValidateViewEntityTypes(stmt *ast.CreateViewEntityStmt) []str errors = append(errors, v.Message) } - columns, warnings := e.InferOQLTypes(stmt.Query.RawQuery, stmt.Attributes) + columns, warnings := inferOQLTypes(ctx, stmt.Query.RawQuery, stmt.Attributes) errors = append(errors, warnings...) // Match columns to declared attributes by position (OQL columns map 1:1 to attributes) @@ -326,6 +332,11 @@ func (e *Executor) ValidateViewEntityTypes(stmt *ast.CreateViewEntityStmt) []str return errors } +// ValidateViewEntityTypes validates that declared attribute types match inferred OQL types. +func (e *Executor) ValidateViewEntityTypes(stmt *ast.CreateViewEntityStmt) []string { + return validateViewEntityTypes(e.newExecContext(context.Background()), stmt) +} + // extractSelectClause extracts the SELECT clause from an OQL query. // Handles subqueries by tracking parenthesis depth to find the main FROM clause. func extractSelectClause(oql string) string { @@ -481,18 +492,18 @@ func parseSelectColumns(selectClause string) []string { } // inferTypeFromExpression infers the data type from an OQL expression. -func (e *Executor) inferTypeFromExpression(expr string, col *OQLColumnInfo, aliasMap map[string]string) ast.DataType { +func inferTypeFromExpression(ctx *ExecContext, expr string, col *OQLColumnInfo, aliasMap map[string]string) ast.DataType { expr = strings.TrimSpace(expr) // Check for aggregate functions - if aggType := e.inferAggregateType(expr, col, aliasMap); aggType.Kind != ast.TypeUnknown { + if aggType := inferAggregateType(ctx, expr, col, aliasMap); aggType.Kind != ast.TypeUnknown { return aggType } // Check for attribute reference: [Entity/Attribute] or [Module.Entity/Attribute] attrPattern := regexp.MustCompile(`^\[([^\]]+)\]$`) if match := attrPattern.FindStringSubmatch(expr); match != nil { - return e.inferAttributeType(match[1], col) + return inferAttributeType(ctx, match[1], col) } // Check for MDL-style alias.attribute reference (e.g., p.Name, o.OrderDate) @@ -504,7 +515,7 @@ func (e *Executor) inferTypeFromExpression(expr string, col *OQLColumnInfo, alia // Resolve alias to entity and look up attribute col.SourceEntity = entityName col.SourceAttr = attrName - return e.inferAttributeTypeFromEntity(entityName, attrName) + return inferAttributeTypeFromEntity(ctx, entityName, attrName) } } @@ -531,7 +542,7 @@ func (e *Executor) inferTypeFromExpression(expr string, col *OQLColumnInfo, alia } // inferAttributeTypeFromEntity looks up an attribute's type from a qualified entity name. -func (e *Executor) inferAttributeTypeFromEntity(entityQualifiedName, attrName string) ast.DataType { +func inferAttributeTypeFromEntity(ctx *ExecContext, entityQualifiedName, attrName string) ast.DataType { parts := strings.Split(entityQualifiedName, ".") if len(parts) != 2 { return ast.DataType{Kind: ast.TypeUnknown} @@ -540,7 +551,7 @@ func (e *Executor) inferAttributeTypeFromEntity(entityQualifiedName, attrName st moduleName := parts[0] entityName := parts[1] - entity, err := e.findEntity(moduleName, entityName) + entity, err := findEntity(ctx, moduleName, entityName) if err != nil { return ast.DataType{Kind: ast.TypeUnknown} } @@ -555,7 +566,7 @@ func (e *Executor) inferAttributeTypeFromEntity(entityQualifiedName, attrName st } // inferAggregateType infers the return type of aggregate functions. -func (e *Executor) inferAggregateType(expr string, col *OQLColumnInfo, aliasMap map[string]string) ast.DataType { +func inferAggregateType(ctx *ExecContext, expr string, col *OQLColumnInfo, aliasMap map[string]string) ast.DataType { upperExpr := strings.ToUpper(strings.TrimSpace(expr)) // COUNT(*) or COUNT(expression) → Integer (Mendix OQL COUNT returns Integer) @@ -571,7 +582,7 @@ func (e *Executor) inferAggregateType(expr string, col *OQLColumnInfo, aliasMap col.AggregateFunc = "SUM" innerArg := extractFunctionArg(expr) if innerArg != "" { - innerType := e.inferTypeFromExpression(innerArg, &OQLColumnInfo{}, aliasMap) + innerType := inferTypeFromExpression(ctx, innerArg, &OQLColumnInfo{}, aliasMap) if innerType.Kind == ast.TypeInteger || innerType.Kind == ast.TypeLong { return innerType } @@ -592,7 +603,7 @@ func (e *Executor) inferAggregateType(expr string, col *OQLColumnInfo, aliasMap col.AggregateFunc = strings.Split(upperExpr, "(")[0] innerArg := extractFunctionArg(expr) if innerArg != "" { - innerType := e.inferTypeFromExpression(innerArg, &OQLColumnInfo{}, aliasMap) + innerType := inferTypeFromExpression(ctx, innerArg, &OQLColumnInfo{}, aliasMap) if innerType.Kind != ast.TypeUnknown { return innerType } @@ -614,7 +625,7 @@ func (e *Executor) inferAggregateType(expr string, col *OQLColumnInfo, aliasMap } // inferAttributeType looks up an attribute's type from the referenced entity. -func (e *Executor) inferAttributeType(attrPath string, col *OQLColumnInfo) ast.DataType { +func inferAttributeType(ctx *ExecContext, attrPath string, col *OQLColumnInfo) ast.DataType { // Parse [Module.Entity/Attribute] or [Entity/Attribute] parts := strings.Split(attrPath, "/") if len(parts) != 2 { @@ -641,7 +652,7 @@ func (e *Executor) inferAttributeType(attrPath string, col *OQLColumnInfo) ast.D } // Look up the entity in the project - entity, err := e.findEntity(moduleName, entityName) + entity, err := findEntity(ctx, moduleName, entityName) if err != nil { return ast.DataType{Kind: ast.TypeUnknown} } @@ -656,15 +667,16 @@ func (e *Executor) inferAttributeType(attrPath string, col *OQLColumnInfo) ast.D return ast.DataType{Kind: ast.TypeUnknown} } -// findEntity looks up an entity by module and name. -func (e *Executor) findEntity(moduleName, entityName string) (*domainmodel.Entity, error) { +// oqlFindEntity looks up an entity by module and name. +func findEntity(ctx *ExecContext, moduleName, entityName string) (*domainmodel.Entity, error) { + e := ctx.executor // Get all entities dms, err := e.reader.ListDomainModels() if err != nil { return nil, err } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, err } diff --git a/mdl/executor/register_stubs.go b/mdl/executor/register_stubs.go index 35dfc38c..d1b250d7 100644 --- a/mdl/executor/register_stubs.go +++ b/mdl/executor/register_stubs.go @@ -320,7 +320,7 @@ func registerDataTransformerHandlers(r *Registry) { func registerQueryHandlers(r *Registry) { r.Register(&ast.ShowStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execShow(stmt.(*ast.ShowStmt)) + return execShow(ctx, stmt.(*ast.ShowStmt)) }) r.Register(&ast.ShowWidgetsStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { return execShowWidgets(ctx, stmt.(*ast.ShowWidgetsStmt)) @@ -329,13 +329,13 @@ func registerQueryHandlers(r *Registry) { return execUpdateWidgets(ctx, stmt.(*ast.UpdateWidgetsStmt)) }) r.Register(&ast.SelectStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execCatalogQuery(stmt.(*ast.SelectStmt).Query) + return execCatalogQuery(ctx, stmt.(*ast.SelectStmt).Query) }) r.Register(&ast.DescribeStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDescribe(stmt.(*ast.DescribeStmt)) + return execDescribe(ctx, stmt.(*ast.DescribeStmt)) }) r.Register(&ast.DescribeCatalogTableStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execDescribeCatalogTable(stmt.(*ast.DescribeCatalogTableStmt)) + return execDescribeCatalogTable(ctx, stmt.(*ast.DescribeCatalogTableStmt)) }) // NOTE: ShowFeaturesStmt was missing from the original type-switch // (pre-existing bug). Adding it here to fix the dead-code path. @@ -364,10 +364,10 @@ func registerRepositoryHandlers(r *Registry) { return execRefresh(ctx) }) r.Register(&ast.RefreshCatalogStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execRefreshCatalogStmt(stmt.(*ast.RefreshCatalogStmt)) + return execRefreshCatalogStmt(ctx, stmt.(*ast.RefreshCatalogStmt)) }) r.Register(&ast.SearchStmt{}, func(ctx *ExecContext, stmt ast.Statement) error { - return ctx.executor.execSearch(stmt.(*ast.SearchStmt)) + return execSearch(ctx, stmt.(*ast.SearchStmt)) }) } diff --git a/mdl/executor/validate.go b/mdl/executor/validate.go index 2e24cb5d..11591068 100644 --- a/mdl/executor/validate.go +++ b/mdl/executor/validate.go @@ -4,6 +4,7 @@ package executor import ( + "context" "fmt" "strings" @@ -150,9 +151,10 @@ func (sc *scriptContext) has(name string) bool { sc.microflows[name] || sc.pages[name] || sc.snippets[name] } -// ValidateProgram validates all statements in a program, skipping references +// validateProgram validates all statements in a program, skipping references // to objects that are defined within the script itself. -func (e *Executor) ValidateProgram(prog *ast.Program) []error { +func validateProgram(ctx *ExecContext, prog *ast.Program) []error { + e := ctx.executor if e.reader == nil { return []error{mdlerrors.NewNotConnected()} } @@ -164,20 +166,27 @@ func (e *Executor) ValidateProgram(prog *ast.Program) []error { // Validate each statement var errors []error for i, stmt := range prog.Statements { - if err := e.validateWithContext(stmt, sc); err != nil { + if err := validateWithContext(ctx, stmt, sc); err != nil { errors = append(errors, fmt.Errorf("statement %d: %w", i+1, err)) } } return errors } +// ValidateProgram validates all statements in a program, skipping references +// to objects that are defined within the script itself. +func (e *Executor) ValidateProgram(prog *ast.Program) []error { + return validateProgram(e.newExecContext(context.Background()), prog) +} + // validateWithContext validates a statement, considering objects defined in the script. -func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) error { +func validateWithContext(ctx *ExecContext, stmt ast.Statement, sc *scriptContext) error { + e := ctx.executor switch s := stmt.(type) { // Statements that reference modules case *ast.CreateEntityStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } @@ -196,7 +205,7 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er // Check if enumeration exists (in project or script) enumQN := enumRef.String() if !sc.enumerations[enumQN] { - if !e.enumerationExists(enumQN) { + if !enumerationExists(ctx, enumQN) { return mdlerrors.NewNotFoundMsg("enumeration", enumQN, fmt.Sprintf("attribute '%s': enumeration not found: %s", attr.Name, enumQN)) } } @@ -216,42 +225,42 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er } case *ast.CreateAssociationStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } // Check parent and child entity references if s.Parent.Module != "" && !sc.modules[s.Parent.Module] { - if _, err := e.findModule(s.Parent.Module); err != nil { + if _, err := findModule(ctx, s.Parent.Module); err != nil { return mdlerrors.NewNotFoundMsg("module", s.Parent.Module, "parent entity module not found: "+s.Parent.Module) } } if s.Child.Module != "" && !sc.modules[s.Child.Module] { - if _, err := e.findModule(s.Child.Module); err != nil { + if _, err := findModule(ctx, s.Child.Module); err != nil { return mdlerrors.NewNotFoundMsg("module", s.Child.Module, "child entity module not found: "+s.Child.Module) } } case *ast.CreateImageCollectionStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } case *ast.DropImageCollectionStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } case *ast.CreateEnumerationStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } case *ast.CreateMicroflowStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } @@ -261,18 +270,18 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er s.Name.String(), strings.Join(validationErrors, "\n - ")) } // Validate references inside microflow body (pages, microflows, java actions, entities) - if refErrors := e.validateMicroflowReferences(s, sc); len(refErrors) > 0 { + if refErrors := validateMicroflowReferences(ctx, s, sc); len(refErrors) > 0 { return mdlerrors.NewValidationf("microflow '%s' has reference errors:\n - %s", s.Name.String(), strings.Join(refErrors, "\n - ")) } case *ast.CreatePageStmtV3: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } // Validate widget references (DataSource, Action, Snippet) - if refErrors := e.validateWidgetReferences(s.Widgets, sc); len(refErrors) > 0 { + if refErrors := validateWidgetReferences(ctx, s.Widgets, sc); len(refErrors) > 0 { return mdlerrors.NewValidationf("page '%s' has reference errors:\n - %s", s.Name.String(), strings.Join(refErrors, "\n - ")) } @@ -283,12 +292,12 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er } case *ast.CreateSnippetStmtV3: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } // Validate widget references (DataSource, Action, Snippet) - if refErrors := e.validateWidgetReferences(s.Widgets, sc); len(refErrors) > 0 { + if refErrors := validateWidgetReferences(ctx, s.Widgets, sc); len(refErrors) > 0 { return mdlerrors.NewValidationf("snippet '%s' has reference errors:\n - %s", s.Name.String(), strings.Join(refErrors, "\n - ")) } @@ -299,18 +308,18 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er } case *ast.CreateViewEntityStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } // Validate OQL types match declared attribute types - if typeErrors := e.ValidateViewEntityTypes(s); len(typeErrors) > 0 { + if typeErrors := validateViewEntityTypes(ctx, s); len(typeErrors) > 0 { return mdlerrors.NewValidationf("view entity '%s' has type mismatches:\n - %s", s.Name.String(), strings.Join(typeErrors, "\n - ")) } case *ast.AlterEntityStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } @@ -325,7 +334,7 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er } enumQN := enumRef.String() if !sc.enumerations[enumQN] { - if !e.enumerationExists(enumQN) { + if !enumerationExists(ctx, enumQN) { return mdlerrors.NewNotFoundMsg("enumeration", enumQN, fmt.Sprintf("attribute '%s': enumeration not found: %s", attr.Name, enumQN)) } } @@ -333,14 +342,14 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er } case *ast.DropEntityStmt: if s.Name.Module != "" && !sc.modules[s.Name.Module] { - if _, err := e.findModule(s.Name.Module); err != nil { + if _, err := findModule(ctx, s.Name.Module); err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } } case *ast.DropModuleStmt: // For DROP, check if module exists in project OR will be created in script if !sc.modules[s.Name] { - if _, err := e.findModule(s.Name); err != nil { + if _, err := findModule(ctx, s.Name); err != nil { return mdlerrors.NewNotFound("module", s.Name) } } @@ -362,16 +371,22 @@ func (e *Executor) validateWithContext(stmt ast.Statement, sc *scriptContext) er return nil } + _ = e // suppress unused warning if e not referenced in all paths return nil } -// Validate checks if a statement's references are valid without executing it. +// validate checks if a statement's references are valid without executing it. // This requires being connected to a project. // Note: For validating entire programs with proper handling of script-defined objects, -// use ValidateProgram instead. -func (e *Executor) Validate(stmt ast.Statement) error { +// use validateProgram instead. +func validate(ctx *ExecContext, stmt ast.Statement) error { // Use validateWithContext with an empty script context for single statements - return e.validateWithContext(stmt, newScriptContext()) + return validateWithContext(ctx, stmt, newScriptContext()) +} + +// Validate checks if a statement's references are valid without executing it. +func (e *Executor) Validate(stmt ast.Statement) error { + return validate(e.newExecContext(context.Background()), stmt) } // ---------------------------------------------------------------------------- @@ -380,7 +395,8 @@ func (e *Executor) Validate(stmt ast.Statement) error { // validateMicroflowReferences validates that all qualified name references in a // microflow body (pages, microflows, java actions, entities) point to existing objects. -func (e *Executor) validateMicroflowReferences(s *ast.CreateMicroflowStmt, sc *scriptContext) []string { +func validateMicroflowReferences(ctx *ExecContext, s *ast.CreateMicroflowStmt, sc *scriptContext) []string { + e := ctx.executor if e.reader == nil || len(s.Body) == 0 { return nil } @@ -396,7 +412,7 @@ func (e *Executor) validateMicroflowReferences(s *ast.CreateMicroflowStmt, sc *s var errors []string if len(refs.pages) > 0 { - known := e.buildPageQualifiedNames() + known := buildPageQualifiedNames(ctx) for _, ref := range refs.pages { if !known[ref] && !sc.pages[ref] { errors = append(errors, fmt.Sprintf("page not found: %s (referenced by SHOW PAGE)", ref)) @@ -405,7 +421,7 @@ func (e *Executor) validateMicroflowReferences(s *ast.CreateMicroflowStmt, sc *s } if len(refs.microflows) > 0 { - known := e.buildMicroflowQualifiedNames() + known := buildMicroflowQualifiedNames(ctx) for _, ref := range refs.microflows { if !known[ref] && !sc.microflows[ref] { errors = append(errors, fmt.Sprintf("microflow not found: %s (referenced by CALL MICROFLOW)", ref)) @@ -414,7 +430,7 @@ func (e *Executor) validateMicroflowReferences(s *ast.CreateMicroflowStmt, sc *s } if len(refs.javaActions) > 0 { - known := e.buildJavaActionQualifiedNames() + known := buildJavaActionQualifiedNames(ctx) for _, ref := range refs.javaActions { if !known[ref] { errors = append(errors, fmt.Sprintf("java action not found: %s (referenced by CALL JAVA ACTION)", ref)) @@ -423,7 +439,7 @@ func (e *Executor) validateMicroflowReferences(s *ast.CreateMicroflowStmt, sc *s } if len(refs.entities) > 0 { - known := e.buildEntityQualifiedNames() + known := buildEntityQualifiedNames(ctx) for _, ref := range refs.entities { if !known[ref.name] && !sc.entities[ref.name] { errors = append(errors, fmt.Sprintf("entity not found: %s (referenced by %s)", ref.name, ref.source)) From 678cbb2dff19080b34e48b0b8f6f2b178126c3d6 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 18:11:11 +0200 Subject: [PATCH 14/16] refactor: remove 233 unused Executor wrapper methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 32 remaining e.method() calls in free functions to use direct free function calls. Convert findImageCollection to *ExecContext. Remove 233 wrapper methods that no longer have callers. Executor method count: 295 → 62 (21 lifecycle/API + 38 required wrappers for exported API, tests, and internal callers + 3 infrastructure). --- mdl/executor/cmd_agenteditor_agents.go | 12 --- mdl/executor/cmd_agenteditor_kbs.go | 12 --- mdl/executor/cmd_agenteditor_mcpservices.go | 12 --- mdl/executor/cmd_agenteditor_models.go | 12 --- mdl/executor/cmd_alter_page.go | 4 +- mdl/executor/cmd_alter_workflow.go | 8 +- mdl/executor/cmd_associations.go | 24 ------ mdl/executor/cmd_businessevents.go | 16 ---- mdl/executor/cmd_catalog.go | 5 -- mdl/executor/cmd_constants.go | 15 ---- mdl/executor/cmd_context.go | 48 ------------ mdl/executor/cmd_contract.go | 52 ------------- mdl/executor/cmd_datatransformer.go | 10 +-- mdl/executor/cmd_dbconnection.go | 12 --- mdl/executor/cmd_diff.go | 5 +- mdl/executor/cmd_diff_local.go | 3 +- mdl/executor/cmd_domainmodel_elk.go | 6 +- mdl/executor/cmd_entities.go | 18 +---- mdl/executor/cmd_entities_access.go | 16 ---- mdl/executor/cmd_entities_describe.go | 24 ------ mdl/executor/cmd_enumerations.go | 17 ---- mdl/executor/cmd_export_mappings.go | 11 --- mdl/executor/cmd_features.go | 11 --- mdl/executor/cmd_fragments.go | 25 +----- mdl/executor/cmd_imagecollections.go | 37 ++------- mdl/executor/cmd_import.go | 5 -- mdl/executor/cmd_import_mappings.go | 11 --- mdl/executor/cmd_javaactions.go | 11 --- mdl/executor/cmd_javascript_actions.go | 11 --- mdl/executor/cmd_jsonstructures.go | 11 --- mdl/executor/cmd_languages.go | 4 - mdl/executor/cmd_layouts.go | 4 - mdl/executor/cmd_lint.go | 7 -- mdl/executor/cmd_mermaid.go | 2 +- mdl/executor/cmd_microflows_create.go | 6 +- mdl/executor/cmd_microflows_show.go | 24 ------ mdl/executor/cmd_misc.go | 36 --------- mdl/executor/cmd_modules.go | 19 +---- mdl/executor/cmd_move.go | 2 +- mdl/executor/cmd_navigation.go | 26 ------- mdl/executor/cmd_odata.go | 36 --------- mdl/executor/cmd_oql_plan.go | 4 - mdl/executor/cmd_pages_builder.go | 4 - mdl/executor/cmd_pages_create_v3.go | 2 +- mdl/executor/cmd_pages_describe.go | 20 ----- mdl/executor/cmd_pages_show.go | 4 - mdl/executor/cmd_published_rest.go | 12 +-- mdl/executor/cmd_rest_clients.go | 10 +-- mdl/executor/cmd_search.go | 16 ---- mdl/executor/cmd_security.go | 48 ------------ mdl/executor/cmd_security_write.go | 86 +-------------------- mdl/executor/cmd_settings.go | 26 ------- mdl/executor/cmd_snippets.go | 4 - mdl/executor/cmd_sql.go | 35 --------- mdl/executor/cmd_structure.go | 6 -- mdl/executor/cmd_styling.go | 29 +------ mdl/executor/cmd_widgets.go | 11 --- mdl/executor/cmd_workflows.go | 16 ---- mdl/executor/cmd_workflows_write.go | 11 --- mdl/executor/executor_connect.go | 13 ---- mdl/executor/format.go | 8 -- mdl/executor/helpers.go | 63 +-------------- mdl/executor/hierarchy.go | 7 -- mdl/executor/oql_type_inference.go | 11 --- 64 files changed, 31 insertions(+), 1045 deletions(-) diff --git a/mdl/executor/cmd_agenteditor_agents.go b/mdl/executor/cmd_agenteditor_agents.go index 0fa4362c..15d2e2fc 100644 --- a/mdl/executor/cmd_agenteditor_agents.go +++ b/mdl/executor/cmd_agenteditor_agents.go @@ -8,7 +8,6 @@ package executor import ( - "context" "fmt" "strings" @@ -254,14 +253,3 @@ func findAgentEditorAgent(ctx *ExecContext, moduleName, agentName string) *agent // --- Executor method wrappers for backward compatibility --- -func (e *Executor) showAgentEditorAgents(moduleName string) error { - return showAgentEditorAgents(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeAgentEditorAgent(name ast.QualifiedName) error { - return describeAgentEditorAgent(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findAgentEditorAgent(moduleName, agentName string) *agenteditor.Agent { - return findAgentEditorAgent(e.newExecContext(context.Background()), moduleName, agentName) -} diff --git a/mdl/executor/cmd_agenteditor_kbs.go b/mdl/executor/cmd_agenteditor_kbs.go index 8009613d..2c0bda45 100644 --- a/mdl/executor/cmd_agenteditor_kbs.go +++ b/mdl/executor/cmd_agenteditor_kbs.go @@ -9,7 +9,6 @@ package executor import ( - "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -150,14 +149,3 @@ func findAgentEditorKnowledgeBase(ctx *ExecContext, moduleName, kbName string) * // --- Executor method wrappers for backward compatibility --- -func (e *Executor) showAgentEditorKnowledgeBases(moduleName string) error { - return showAgentEditorKnowledgeBases(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeAgentEditorKnowledgeBase(name ast.QualifiedName) error { - return describeAgentEditorKnowledgeBase(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findAgentEditorKnowledgeBase(moduleName, kbName string) *agenteditor.KnowledgeBase { - return findAgentEditorKnowledgeBase(e.newExecContext(context.Background()), moduleName, kbName) -} diff --git a/mdl/executor/cmd_agenteditor_mcpservices.go b/mdl/executor/cmd_agenteditor_mcpservices.go index 907b758e..24c961a9 100644 --- a/mdl/executor/cmd_agenteditor_mcpservices.go +++ b/mdl/executor/cmd_agenteditor_mcpservices.go @@ -9,7 +9,6 @@ package executor import ( - "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -134,14 +133,3 @@ func findAgentEditorConsumedMCPService(ctx *ExecContext, moduleName, svcName str // --- Executor method wrappers for backward compatibility --- -func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { - return showAgentEditorConsumedMCPServices(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeAgentEditorConsumedMCPService(name ast.QualifiedName) error { - return describeAgentEditorConsumedMCPService(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findAgentEditorConsumedMCPService(moduleName, svcName string) *agenteditor.ConsumedMCPService { - return findAgentEditorConsumedMCPService(e.newExecContext(context.Background()), moduleName, svcName) -} diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index bdf1d1d5..4747913c 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -9,7 +9,6 @@ package executor import ( - "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -156,14 +155,3 @@ func findAgentEditorModel(ctx *ExecContext, moduleName, modelName string) *agent // --- Executor method wrappers for backward compatibility --- -func (e *Executor) showAgentEditorModels(moduleName string) error { - return showAgentEditorModels(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeAgentEditorModel(name ast.QualifiedName) error { - return describeAgentEditorModel(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findAgentEditorModel(moduleName, modelName string) *agenteditor.Model { - return findAgentEditorModel(e.newExecContext(context.Background()), moduleName, modelName) -} diff --git a/mdl/executor/cmd_alter_page.go b/mdl/executor/cmd_alter_page.go index 35d2fbdc..be1b79bc 100644 --- a/mdl/executor/cmd_alter_page.go +++ b/mdl/executor/cmd_alter_page.go @@ -38,14 +38,14 @@ func execAlterPage(ctx *ExecContext, s *ast.AlterPageStmt) error { } if containerType == "SNIPPET" { - snippet, modID, err := e.findSnippetByName(s.PageName, h) + snippet, modID, err := findSnippetByName(ctx, s.PageName, h) if err != nil { return err } unitID = snippet.ID containerID = modID } else { - page, err := e.findPageByName(s.PageName, h) + page, err := findPageByName(ctx, s.PageName, h) if err != nil { return err } diff --git a/mdl/executor/cmd_alter_workflow.go b/mdl/executor/cmd_alter_workflow.go index a1e79a99..f73f183b 100644 --- a/mdl/executor/cmd_alter_workflow.go +++ b/mdl/executor/cmd_alter_workflow.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -31,7 +30,7 @@ func execAlterWorkflow(ctx *ExecContext, s *ast.AlterWorkflowStmt) error { } // Version pre-check: workflows require Mendix 9.12+ - if err := e.checkFeature("workflows", "basic", + if err := checkFeature(ctx, "workflows", "basic", "ALTER WORKFLOW", "upgrade your project to Mendix 9.12+ to use workflows"); err != nil { return err @@ -147,11 +146,6 @@ func execAlterWorkflow(ctx *ExecContext, s *ast.AlterWorkflowStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { - return execAlterWorkflow(e.newExecContext(context.Background()), s) -} - // applySetWorkflowProperty sets a workflow-level property in raw BSON. func applySetWorkflowProperty(doc *bson.D, op *ast.SetWorkflowPropertyOp) error { switch op.Property { diff --git a/mdl/executor/cmd_associations.go b/mdl/executor/cmd_associations.go index eb6119c9..d6a16be5 100644 --- a/mdl/executor/cmd_associations.go +++ b/mdl/executor/cmd_associations.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -480,26 +479,3 @@ func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { // --- Executor method wrappers for callers not yet migrated --- -func (e *Executor) execCreateAssociation(s *ast.CreateAssociationStmt) error { - return execCreateAssociation(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execAlterAssociation(s *ast.AlterAssociationStmt) error { - return execAlterAssociation(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDropAssociation(s *ast.DropAssociationStmt) error { - return execDropAssociation(e.newExecContext(context.Background()), s) -} - -func (e *Executor) showAssociations(moduleName string) error { - return showAssociations(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) showAssociation(name *ast.QualifiedName) error { - return showAssociation(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeAssociation(name ast.QualifiedName) error { - return describeAssociation(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_businessevents.go b/mdl/executor/cmd_businessevents.go index 68b4bbb6..b42d3bdb 100644 --- a/mdl/executor/cmd_businessevents.go +++ b/mdl/executor/cmd_businessevents.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -424,18 +423,3 @@ func generateChannelName() string { // Executor wrappers for unmigrated callers. -func (e *Executor) showBusinessEventServices(inModule string) error { - return showBusinessEventServices(e.newExecContext(context.Background()), inModule) -} - -func (e *Executor) showBusinessEventClients(inModule string) error { - return showBusinessEventClients(e.newExecContext(context.Background()), inModule) -} - -func (e *Executor) showBusinessEvents(inModule string) error { - return showBusinessEvents(e.newExecContext(context.Background()), inModule) -} - -func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { - return describeBusinessEventService(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_catalog.go b/mdl/executor/cmd_catalog.go index 4682b530..685783c3 100644 --- a/mdl/executor/cmd_catalog.go +++ b/mdl/executor/cmd_catalog.go @@ -764,11 +764,6 @@ func preWarmCache(ctx *ExecContext) { } } -// PreWarmCache ensures all caches are populated before parallel operations. -func (e *Executor) PreWarmCache() { - preWarmCache(e.newExecContext(context.Background())) -} - // execSearch handles SEARCH 'query' command. func execSearch(ctx *ExecContext, stmt *ast.SearchStmt) error { e := ctx.executor diff --git a/mdl/executor/cmd_constants.go b/mdl/executor/cmd_constants.go index 269824bb..dbfabbb4 100644 --- a/mdl/executor/cmd_constants.go +++ b/mdl/executor/cmd_constants.go @@ -82,11 +82,6 @@ func showConstants(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// showConstants is an Executor method wrapper for callers not yet migrated. -func (e *Executor) showConstants(moduleName string) error { - return showConstants(e.newExecContext(context.Background()), moduleName) -} - // describeConstant handles DESCRIBE CONSTANT command. func describeConstant(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -114,11 +109,6 @@ func describeConstant(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("constant", name.String()) } -// describeConstant is an Executor method wrapper for callers not yet migrated. -func (e *Executor) describeConstant(name ast.QualifiedName) error { - return describeConstant(e.newExecContext(context.Background()), name) -} - // outputConstantMDL outputs a constant definition in MDL format. func outputConstantMDL(ctx *ExecContext, c *model.Constant, moduleName string) error { // Format default value based on type @@ -453,11 +443,6 @@ func showConstantValues(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// showConstantValues is an Executor method wrapper for callers not yet migrated. -func (e *Executor) showConstantValues(moduleName string) error { - return showConstantValues(e.newExecContext(context.Background()), moduleName) -} - // dropConstant handles DROP CONSTANT command. func dropConstant(ctx *ExecContext, stmt *ast.DropConstantStmt) error { e := ctx.executor diff --git a/mdl/executor/cmd_context.go b/mdl/executor/cmd_context.go index 000dd27b..34cc254c 100644 --- a/mdl/executor/cmd_context.go +++ b/mdl/executor/cmd_context.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -610,50 +609,3 @@ func assembleODataServiceContext(ctx *ExecContext, out *strings.Builder, name st // --- Executor method wrappers for backward compatibility --- -func (e *Executor) execShowContext(s *ast.ShowStmt) error { - return execShowContext(e.newExecContext(context.Background()), s) -} - -func (e *Executor) detectElementType(name string) (string, error) { - return detectElementType(e.newExecContext(context.Background()), name) -} - -func (e *Executor) assembleMicroflowContext(out *strings.Builder, name string, depth int) { - assembleMicroflowContext(e.newExecContext(context.Background()), out, name, depth) -} - -func (e *Executor) addCallees(out *strings.Builder, name string, maxDepth, currentDepth int) { - addCallees(e.newExecContext(context.Background()), out, name, maxDepth, currentDepth) -} - -func (e *Executor) assembleEntityContext(out *strings.Builder, name string, depth int) { - assembleEntityContext(e.newExecContext(context.Background()), out, name, depth) -} - -func (e *Executor) assemblePageContext(out *strings.Builder, name string, depth int) { - assemblePageContext(e.newExecContext(context.Background()), out, name, depth) -} - -func (e *Executor) assembleEnumerationContext(out *strings.Builder, name string) { - assembleEnumerationContext(e.newExecContext(context.Background()), out, name) -} - -func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, depth int) { - assembleSnippetContext(e.newExecContext(context.Background()), out, name, depth) -} - -func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) { - assembleJavaActionContext(e.newExecContext(context.Background()), out, name) -} - -func (e *Executor) assembleODataClientContext(out *strings.Builder, name string) { - assembleODataClientContext(e.newExecContext(context.Background()), out, name) -} - -func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, depth int) { - assembleWorkflowContext(e.newExecContext(context.Background()), out, name, depth) -} - -func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string) { - assembleODataServiceContext(e.newExecContext(context.Background()), out, name) -} diff --git a/mdl/executor/cmd_contract.go b/mdl/executor/cmd_contract.go index 3141cd75..6a5a048c 100644 --- a/mdl/executor/cmd_contract.go +++ b/mdl/executor/cmd_contract.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -1432,54 +1431,3 @@ func asyncTypeString(p *mpr.AsyncAPIProperty) string { // --- Executor method wrappers for backward compatibility --- -func (e *Executor) showContractEntities(name *ast.QualifiedName) error { - return showContractEntities(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showContractActions(name *ast.QualifiedName) error { - return showContractActions(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeContractEntity(name ast.QualifiedName, format string) error { - return describeContractEntity(e.newExecContext(context.Background()), name, format) -} - -func (e *Executor) describeContractAction(name ast.QualifiedName, format string) error { - return describeContractAction(e.newExecContext(context.Background()), name, format) -} - -func (e *Executor) outputContractEntityMDL(et *mpr.EdmEntityType, svcQN string, doc *mpr.EdmxDocument) error { - return outputContractEntityMDL(e.newExecContext(context.Background()), et, svcQN, doc) -} - -func (e *Executor) parseServiceContract(name ast.QualifiedName) (*mpr.EdmxDocument, string, error) { - return parseServiceContract(e.newExecContext(context.Background()), name) -} - -func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) error { - return createExternalEntities(e.newExecContext(context.Background()), s) -} - -func (e *Executor) createPrimitiveCollectionNPEs(dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string) int { - return createPrimitiveCollectionNPEs(e.newExecContext(context.Background()), dm, doc, typeByQualified, esMap, serviceRef) -} - -func (e *Executor) createNavigationAssociations(dm *domainmodel.DomainModel, doc *mpr.EdmxDocument, typeByQualified map[string]*mpr.EdmEntityType, esMap map[string]string, serviceRef string) int { - return createNavigationAssociations(e.newExecContext(context.Background()), dm, doc, typeByQualified, esMap, serviceRef) -} - -func (e *Executor) showContractChannels(name *ast.QualifiedName) error { - return showContractChannels(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showContractMessages(name *ast.QualifiedName) error { - return showContractMessages(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeContractMessage(name ast.QualifiedName) error { - return describeContractMessage(e.newExecContext(context.Background()), name) -} - -func (e *Executor) parseAsyncAPIContract(name ast.QualifiedName) (*mpr.AsyncAPIDocument, string, error) { - return parseAsyncAPIContract(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_datatransformer.go b/mdl/executor/cmd_datatransformer.go index 882369d9..27d6be45 100644 --- a/mdl/executor/cmd_datatransformer.go +++ b/mdl/executor/cmd_datatransformer.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -115,7 +114,7 @@ func execCreateDataTransformer(ctx *ExecContext, s *ast.CreateDataTransformerStm return mdlerrors.NewNotConnectedWrite() } - if err := e.checkFeature("integration", "data_transformer", + if err := checkFeature(ctx, "integration", "data_transformer", "CREATE DATA TRANSFORMER", "upgrade your project to 11.9+"); err != nil { return err @@ -188,10 +187,3 @@ func execDropDataTransformer(ctx *ExecContext, s *ast.DropDataTransformerStmt) e // Executor wrappers for unmigrated callers. -func (e *Executor) listDataTransformers(moduleName string) error { - return listDataTransformers(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeDataTransformer(name ast.QualifiedName) error { - return describeDataTransformer(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_dbconnection.go b/mdl/executor/cmd_dbconnection.go index a2166906..66189392 100644 --- a/mdl/executor/cmd_dbconnection.go +++ b/mdl/executor/cmd_dbconnection.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -340,14 +339,3 @@ func dbTypeToMDLType(bsonType string) string { // Executor wrappers for unmigrated callers. -func (e *Executor) showDatabaseConnections(moduleName string) error { - return showDatabaseConnections(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeDatabaseConnection(name ast.QualifiedName) error { - return describeDatabaseConnection(e.newExecContext(context.Background()), name) -} - -func (e *Executor) outputDatabaseConnectionMDL(conn *model.DatabaseConnection, moduleName string) error { - return outputDatabaseConnectionMDL(e.newExecContext(context.Background()), conn, moduleName) -} diff --git a/mdl/executor/cmd_diff.go b/mdl/executor/cmd_diff.go index 866a0364..f05bd4d0 100644 --- a/mdl/executor/cmd_diff.go +++ b/mdl/executor/cmd_diff.go @@ -232,7 +232,6 @@ func diffViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) (*DiffResult, // diffEnumeration compares a CREATE ENUMERATION statement against the project func diffEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) (*DiffResult, error) { - e := ctx.executor result := &DiffResult{ ObjectType: "Enumeration", ObjectName: s.Name, @@ -240,7 +239,7 @@ func diffEnumeration(ctx *ExecContext, s *ast.CreateEnumerationStmt) (*DiffResul } // Try to find existing enumeration - existingEnum := e.findEnumeration(s.Name.Module, s.Name.Name) + existingEnum := findEnumeration(ctx, s.Name.Module, s.Name.Name) if existingEnum == nil { result.IsNew = true return result, nil @@ -316,7 +315,7 @@ func diffMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) (*DiffResult, e var buf bytes.Buffer oldOutput := e.output e.output = &buf - e.describeMicroflow(s.Name) + describeMicroflow(ctx, s.Name) e.output = oldOutput result.Current = strings.TrimSuffix(buf.String(), "\n") result.Changes = compareMicroflows(ctx, result.Current, result.Proposed) diff --git a/mdl/executor/cmd_diff_local.go b/mdl/executor/cmd_diff_local.go index 7703e790..b698c8ad 100644 --- a/mdl/executor/cmd_diff_local.go +++ b/mdl/executor/cmd_diff_local.go @@ -497,12 +497,11 @@ func attributeBsonToMDL(_ *ExecContext, raw map[string]any) string { // renderer as DESCRIBE MICROFLOW, so diffs include activity bodies. // Falls back to a header-only stub if parsing fails. func microflowBsonToMDL(ctx *ExecContext, raw map[string]any, qualifiedName string) string { - e := ctx.executor qn := splitQualifiedName(qualifiedName) mf := mpr.ParseMicroflowFromRaw(raw, model.ID(qn.Name), "") entityNames, microflowNames := buildNameLookups(ctx) - return e.renderMicroflowMDL(mf, qn, entityNames, microflowNames, nil) + return renderMicroflowMDL(ctx, mf, qn, entityNames, microflowNames, nil) } // splitQualifiedName parses "Module.Name" into an ast.QualifiedName. diff --git a/mdl/executor/cmd_domainmodel_elk.go b/mdl/executor/cmd_domainmodel_elk.go index 946048e9..9d235643 100644 --- a/mdl/executor/cmd_domainmodel_elk.go +++ b/mdl/executor/cmd_domainmodel_elk.go @@ -192,7 +192,7 @@ func entityFocusELK(ctx *ExecContext, qualifiedName string) error { // If this is a view entity with an OQL query, render query plan instead if classifyEntity(focusEntity) == "view" && focusEntity.OqlQuery != "" { - return e.OqlQueryPlanELK(qualifiedName, focusEntity) + return OqlQueryPlanELK(ctx, qualifiedName, focusEntity) } allEntityNames, _ := buildAllEntityNames(ctx) @@ -517,7 +517,3 @@ func (e *Executor) DomainModelELK(name string) error { return domainModelELK(e.newExecContext(context.Background()), name) } -// EntityFocusELK is the exported Executor method. -func (e *Executor) EntityFocusELK(qualifiedName string) error { - return entityFocusELK(e.newExecContext(context.Background()), qualifiedName) -} diff --git a/mdl/executor/cmd_entities.go b/mdl/executor/cmd_entities.go index 55ed1e95..717a60b7 100644 --- a/mdl/executor/cmd_entities.go +++ b/mdl/executor/cmd_entities.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" @@ -301,7 +300,7 @@ func execCreateViewEntity(ctx *ExecContext, s *ast.CreateViewEntityStmt) error { } // Version pre-check - if err := e.checkFeature("domain_model", "view_entities", + if err := checkFeature(ctx, "domain_model", "view_entities", "CREATE VIEW ENTITY", "upgrade your project to 10.18+ or use a regular entity with a microflow data source"); err != nil { return err @@ -986,18 +985,3 @@ func warnEntityReferences(ctx *ExecContext, entityName string) { // --- Executor method wrappers for callers not yet migrated --- -func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { - return execCreateEntity(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { - return execCreateViewEntity(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { - return execAlterEntity(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { - return execDropEntity(e.newExecContext(context.Background()), s) -} diff --git a/mdl/executor/cmd_entities_access.go b/mdl/executor/cmd_entities_access.go index a43025da..4d0d5b24 100644 --- a/mdl/executor/cmd_entities_access.go +++ b/mdl/executor/cmd_entities_access.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" @@ -218,18 +217,3 @@ func formatAccessRuleResult(ctx *ExecContext, moduleName, entityName string, rol // --- Executor method wrappers for callers not yet migrated --- -func (e *Executor) outputEntityAccessGrants(entity *domainmodel.Entity, moduleName, entityName string) { - outputEntityAccessGrants(e.newExecContext(context.Background()), entity, moduleName, entityName) -} - -func (e *Executor) formatAccessRuleRights(rule *domainmodel.AccessRule, attrNames map[string]string) string { - return formatAccessRuleRights(e.newExecContext(context.Background()), rule, attrNames) -} - -func (e *Executor) resolveEntityMemberAccess(rule *domainmodel.AccessRule, attrNames map[string]string) ([]string, []string) { - return resolveEntityMemberAccess(e.newExecContext(context.Background()), rule, attrNames) -} - -func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNames []string) string { - return formatAccessRuleResult(e.newExecContext(context.Background()), moduleName, entityName, roleNames) -} diff --git a/mdl/executor/cmd_entities_describe.go b/mdl/executor/cmd_entities_describe.go index 1c95d494..99bdfdde 100644 --- a/mdl/executor/cmd_entities_describe.go +++ b/mdl/executor/cmd_entities_describe.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -531,26 +530,3 @@ func lookupMicroflowName(ctx *ExecContext, mfID model.ID) string { // --- Executor method wrappers for callers not yet migrated --- -func (e *Executor) showEntities(moduleName string) error { - return showEntities(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) showEntity(name *ast.QualifiedName) error { - return showEntity(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeEntity(name ast.QualifiedName) error { - return describeEntity(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeEntityToString(name ast.QualifiedName) (string, error) { - return describeEntityToString(e.newExecContext(context.Background()), name) -} - -func (e *Executor) resolveMicroflowByName(qualifiedName string) (model.ID, error) { - return resolveMicroflowByName(e.newExecContext(context.Background()), qualifiedName) -} - -func (e *Executor) lookupMicroflowName(mfID model.ID) string { - return lookupMicroflowName(e.newExecContext(context.Background()), mfID) -} diff --git a/mdl/executor/cmd_enumerations.go b/mdl/executor/cmd_enumerations.go index 583d0c41..7bc3bc08 100644 --- a/mdl/executor/cmd_enumerations.go +++ b/mdl/executor/cmd_enumerations.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -106,11 +105,6 @@ func findEnumeration(ctx *ExecContext, moduleName, enumName string) *model.Enume return nil } -// findEnumeration is an Executor method wrapper for callers not yet migrated. -func (e *Executor) findEnumeration(moduleName, enumName string) *model.Enumeration { - return findEnumeration(e.newExecContext(context.Background()), moduleName, enumName) -} - // execAlterEnumeration handles ALTER ENUMERATION statements. func execAlterEnumeration(ctx *ExecContext, s *ast.AlterEnumerationStmt) error { // TODO: Implement ALTER ENUMERATION @@ -200,11 +194,6 @@ func showEnumerations(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// showEnumerations is an Executor method wrapper for callers not yet migrated. -func (e *Executor) showEnumerations(moduleName string) error { - return showEnumerations(e.newExecContext(context.Background()), moduleName) -} - // describeEnumeration handles DESCRIBE ENUMERATION command. func describeEnumeration(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -249,11 +238,6 @@ func describeEnumeration(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("enumeration", name.String()) } -// describeEnumeration is an Executor method wrapper for callers not yet migrated. -func (e *Executor) describeEnumeration(name ast.QualifiedName) error { - return describeEnumeration(e.newExecContext(context.Background()), name) -} - // mendixReservedWords contains words that cannot be used as enumeration value names. // These are Java reserved words plus Mendix-specific reserved identifiers. // Using any of these triggers CE7247: "The name 'X' is a reserved word." @@ -274,7 +258,6 @@ var mendixReservedWords = map[string]bool{ "true": true, "try": true, "void": true, "volatile": true, "while": true, // Mendix-specific reserved identifiers - "changedby": true, "changeddate": true, "con": true, "context": true, "createddate": true, "currentuser": true, "guid": true, "id": true, "mendixobject": true, "submetaobjectname": true, } diff --git a/mdl/executor/cmd_export_mappings.go b/mdl/executor/cmd_export_mappings.go index 1f9d35cb..7fc3419a 100644 --- a/mdl/executor/cmd_export_mappings.go +++ b/mdl/executor/cmd_export_mappings.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "io" "sort" @@ -79,11 +78,6 @@ func showExportMappings(ctx *ExecContext, inModule string) error { return writeResult(ctx, result) } -// showExportMappings is a wrapper for callers that still use an Executor receiver. -func (e *Executor) showExportMappings(inModule string) error { - return showExportMappings(e.newExecContext(context.Background()), inModule) -} - // describeExportMapping prints the MDL representation of an export mapping. func describeExportMapping(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -133,11 +127,6 @@ func describeExportMapping(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// describeExportMapping is a wrapper for callers that still use an Executor receiver. -func (e *Executor) describeExportMapping(name ast.QualifiedName) error { - return describeExportMapping(e.newExecContext(context.Background()), name) -} - func printExportMappingElement(w io.Writer, elem *model.ExportMappingElement, depth int, isRoot bool) { indent := strings.Repeat(" ", depth) if elem.Kind == "Object" { diff --git a/mdl/executor/cmd_features.go b/mdl/executor/cmd_features.go index 07929a4b..02a74fc7 100644 --- a/mdl/executor/cmd_features.go +++ b/mdl/executor/cmd_features.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -48,11 +47,6 @@ func checkFeature(ctx *ExecContext, area, name, statement, hint string) error { return mdlerrors.NewUnsupported(msg) } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) checkFeature(area, name, statement, hint string) error { - return checkFeature(e.newExecContext(context.Background()), area, name, statement, hint) -} - // execShowFeatures handles SHOW FEATURES, SHOW FEATURES FOR VERSION, and // SHOW FEATURES ADDED SINCE commands. func execShowFeatures(ctx *ExecContext, s *ast.ShowFeaturesStmt) error { @@ -97,11 +91,6 @@ func execShowFeatures(ctx *ExecContext, s *ast.ShowFeaturesStmt) error { return showFeaturesAll(ctx, reg, pv) } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execShowFeatures(s *ast.ShowFeaturesStmt) error { - return execShowFeatures(e.newExecContext(context.Background()), s) -} - func showFeaturesAll(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer) error { features := reg.FeaturesForVersion(pv) if len(features) == 0 { diff --git a/mdl/executor/cmd_fragments.go b/mdl/executor/cmd_fragments.go index c7fb8b94..f822d9fa 100644 --- a/mdl/executor/cmd_fragments.go +++ b/mdl/executor/cmd_fragments.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "io" "sort" @@ -29,11 +28,6 @@ func execDefineFragment(ctx *ExecContext, s *ast.DefineFragmentStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execDefineFragment(s *ast.DefineFragmentStmt) error { - return execDefineFragment(e.newExecContext(context.Background()), s) -} - // showFragments lists all defined fragments in the current session. func showFragments(ctx *ExecContext) error { if len(ctx.Fragments) == 0 { @@ -57,11 +51,6 @@ func showFragments(ctx *ExecContext) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) showFragments() error { - return showFragments(e.newExecContext(context.Background())) -} - // describeFragment outputs a fragment's definition as MDL. func describeFragment(ctx *ExecContext, name ast.QualifiedName) error { if ctx.Fragments == nil { @@ -80,11 +69,6 @@ func describeFragment(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeFragment(name ast.QualifiedName) error { - return describeFragment(e.newExecContext(context.Background()), name) -} - // describeFragmentFrom handles DESCRIBE FRAGMENT FROM PAGE/SNIPPET ... WIDGET ... command. // It finds a named widget in a page or snippet and outputs it as MDL. func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) error { @@ -118,7 +102,7 @@ func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) err if foundPage == nil { return mdlerrors.NewNotFound("page", s.ContainerName.String()) } - rawWidgets = e.getPageWidgetsFromRaw(foundPage.ID) + rawWidgets = getPageWidgetsFromRaw(ctx, foundPage.ID) case "SNIPPET": allSnippets, err := e.reader.ListSnippets() @@ -137,7 +121,7 @@ func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) err if foundSnippet == nil { return mdlerrors.NewNotFound("snippet", s.ContainerName.String()) } - rawWidgets = e.getSnippetWidgetsFromRaw(foundSnippet.ID) + rawWidgets = getSnippetWidgetsFromRaw(ctx, foundSnippet.ID) } // Find the widget by name @@ -151,11 +135,6 @@ func describeFragmentFrom(ctx *ExecContext, s *ast.DescribeFragmentFromStmt) err return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { - return describeFragmentFrom(e.newExecContext(context.Background()), s) -} - // findRawWidgetByName recursively searches the widget tree for a widget with the given name. func findRawWidgetByName(widgets []rawWidget, name string) *rawWidget { for i := range widgets { diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index 14c4e4d6..ca6d524e 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "os" "path/filepath" @@ -29,7 +28,7 @@ func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStm } // Check if image collection already exists - existing := e.findImageCollection(s.Name.Module, s.Name.Name) + existing := findImageCollection(ctx, s.Name.Module, s.Name.Name) if existing != nil { return mdlerrors.NewAlreadyExists("image collection", s.Name.Module+"."+s.Name.Name) } @@ -75,11 +74,6 @@ func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStm return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execCreateImageCollection(s *ast.CreateImageCollectionStmt) error { - return execCreateImageCollection(e.newExecContext(context.Background()), s) -} - // execDropImageCollection handles DROP IMAGE COLLECTION statements. func execDropImageCollection(ctx *ExecContext, s *ast.DropImageCollectionStmt) error { e := ctx.executor @@ -87,7 +81,7 @@ func execDropImageCollection(ctx *ExecContext, s *ast.DropImageCollectionStmt) e return mdlerrors.NewNotConnected() } - ic := e.findImageCollection(s.Name.Module, s.Name.Name) + ic := findImageCollection(ctx, s.Name.Module, s.Name.Name) if ic == nil { return mdlerrors.NewNotFound("image collection", s.Name.String()) } @@ -100,15 +94,9 @@ func execDropImageCollection(ctx *ExecContext, s *ast.DropImageCollectionStmt) e return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execDropImageCollection(s *ast.DropImageCollectionStmt) error { - return execDropImageCollection(e.newExecContext(context.Background()), s) -} - // describeImageCollection handles DESCRIBE IMAGE COLLECTION Module.Name. func describeImageCollection(ctx *ExecContext, name ast.QualifiedName) error { - e := ctx.executor - ic := e.findImageCollection(name.Module, name.Name) + ic := findImageCollection(ctx, name.Module, name.Name) if ic == nil { return mdlerrors.NewNotFound("image collection", name.String()) } @@ -174,11 +162,6 @@ func describeImageCollection(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeImageCollection(name ast.QualifiedName) error { - return describeImageCollection(e.newExecContext(context.Background()), name) -} - // imageFormatToExt converts a Mendix ImageFormat value to a file extension. func imageFormatToExt(format string) string { switch format { @@ -251,19 +234,15 @@ func showImageCollections(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// Executor wrapper for unmigrated callers. -func (e *Executor) showImageCollections(moduleName string) error { - return showImageCollections(e.newExecContext(context.Background()), moduleName) -} - // findImageCollection finds an image collection by module and name. -func findImageCollection(e *Executor, moduleName, collectionName string) *mpr.ImageCollection { +func findImageCollection(ctx *ExecContext, moduleName, collectionName string) *mpr.ImageCollection { + e := ctx.executor collections, err := e.reader.ListImageCollections() if err != nil { return nil } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -278,7 +257,3 @@ func findImageCollection(e *Executor, moduleName, collectionName string) *mpr.Im return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) findImageCollection(moduleName, collectionName string) *mpr.ImageCollection { - return findImageCollection(e, moduleName, collectionName) -} diff --git a/mdl/executor/cmd_import.go b/mdl/executor/cmd_import.go index 5cf0eb08..18a60fbd 100644 --- a/mdl/executor/cmd_import.go +++ b/mdl/executor/cmd_import.go @@ -97,11 +97,6 @@ func execImport(ctx *ExecContext, s *ast.ImportStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execImport(s *ast.ImportStmt) error { - return execImport(e.newExecContext(context.Background()), s) -} - // resolveImportLinks resolves LINK mappings from the AST into AssocInfo structs // by looking up association metadata from the MPR and the Mendix system tables. func resolveImportLinks(ctx *ExecContext, goCtx context.Context, mendixConn *sqllib.Connection, s *ast.ImportStmt) ([]*sqllib.AssocInfo, error) { diff --git a/mdl/executor/cmd_import_mappings.go b/mdl/executor/cmd_import_mappings.go index 84c7392d..d59afdea 100644 --- a/mdl/executor/cmd_import_mappings.go +++ b/mdl/executor/cmd_import_mappings.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "io" "sort" @@ -79,11 +78,6 @@ func showImportMappings(ctx *ExecContext, inModule string) error { return writeResult(ctx, result) } -// showImportMappings is a wrapper for callers that still use an Executor receiver. -func (e *Executor) showImportMappings(inModule string) error { - return showImportMappings(e.newExecContext(context.Background()), inModule) -} - // describeImportMapping prints the MDL representation of an import mapping. func describeImportMapping(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -129,11 +123,6 @@ func describeImportMapping(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// describeImportMapping is a wrapper for callers that still use an Executor receiver. -func (e *Executor) describeImportMapping(name ast.QualifiedName) error { - return describeImportMapping(e.newExecContext(context.Background()), name) -} - // handlingKeyword returns the MDL keyword for a Mendix ObjectHandling value. func handlingKeyword(handling string) string { switch handling { diff --git a/mdl/executor/cmd_javaactions.go b/mdl/executor/cmd_javaactions.go index 45d4954f..854e04dd 100644 --- a/mdl/executor/cmd_javaactions.go +++ b/mdl/executor/cmd_javaactions.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "os" "path/filepath" @@ -67,11 +66,6 @@ func showJavaActions(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// showJavaActions is a wrapper for callers that still use an Executor receiver. -func (e *Executor) showJavaActions(moduleName string) error { - return showJavaActions(e.newExecContext(context.Background()), moduleName) -} - // describeJavaAction handles DESCRIBE JAVA ACTION command - outputs MDL-style representation. func describeJavaAction(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -191,11 +185,6 @@ func describeJavaAction(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// describeJavaAction is a wrapper for callers that still use an Executor receiver. -func (e *Executor) describeJavaAction(name ast.QualifiedName) error { - return describeJavaAction(e.newExecContext(context.Background()), name) -} - // readJavaActionUserCode reads the Java source file and extracts the user code section. func readJavaActionUserCode(mprPath, moduleName, actionName string) string { if mprPath == "" { diff --git a/mdl/executor/cmd_javascript_actions.go b/mdl/executor/cmd_javascript_actions.go index 4e182d61..4898391c 100644 --- a/mdl/executor/cmd_javascript_actions.go +++ b/mdl/executor/cmd_javascript_actions.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "os" "path/filepath" @@ -66,11 +65,6 @@ func showJavaScriptActions(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// showJavaScriptActions is a wrapper for callers that still use an Executor receiver. -func (e *Executor) showJavaScriptActions(moduleName string) error { - return showJavaScriptActions(e.newExecContext(context.Background()), moduleName) -} - // describeJavaScriptAction handles DESCRIBE JAVASCRIPT ACTION command. func describeJavaScriptAction(ctx *ExecContext, name ast.QualifiedName) error { e := ctx.executor @@ -219,11 +213,6 @@ func describeJavaScriptAction(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// describeJavaScriptAction is a wrapper for callers that still use an Executor receiver. -func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { - return describeJavaScriptAction(e.newExecContext(context.Background()), name) -} - // readJavaScriptActionSource reads the JavaScript source file and extracts user code and extra code. func readJavaScriptActionSource(mprPath, moduleName, actionName string) (userCode, extraCode string) { if mprPath == "" { diff --git a/mdl/executor/cmd_jsonstructures.go b/mdl/executor/cmd_jsonstructures.go index 2fb915a0..21ea7daa 100644 --- a/mdl/executor/cmd_jsonstructures.go +++ b/mdl/executor/cmd_jsonstructures.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -70,11 +69,6 @@ func showJsonStructures(ctx *ExecContext, moduleName string) error { return writeResult(ctx, tr) } -// showJsonStructures is a wrapper for callers that still use an Executor receiver. -func (e *Executor) showJsonStructures(moduleName string) error { - return showJsonStructures(e.newExecContext(context.Background()), moduleName) -} - // describeJsonStructure handles DESCRIBE JSON STRUCTURE Module.Name. // Output is re-executable CREATE OR REPLACE MDL followed by the element tree as comments. func describeJsonStructure(ctx *ExecContext, name ast.QualifiedName) error { @@ -140,11 +134,6 @@ func describeJsonStructure(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// describeJsonStructure is a wrapper for callers that still use an Executor receiver. -func (e *Executor) describeJsonStructure(name ast.QualifiedName) error { - return describeJsonStructure(e.newExecContext(context.Background()), name) -} - // collectCustomNameMappings walks the element tree and returns JSON key → ExposedName // mappings where the ExposedName differs from the auto-generated default (capitalizeFirst). func collectCustomNameMappings(elements []*mpr.JsonElement) map[string]string { diff --git a/mdl/executor/cmd_languages.go b/mdl/executor/cmd_languages.go index 239a7ed9..ed6b52b0 100644 --- a/mdl/executor/cmd_languages.go +++ b/mdl/executor/cmd_languages.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" mdlerrors "github.com/mendixlabs/mxcli/mdl/errors" @@ -52,6 +51,3 @@ func showLanguages(ctx *ExecContext) error { // --- Executor method wrapper for backward compatibility --- -func (e *Executor) showLanguages() error { - return showLanguages(e.newExecContext(context.Background())) -} diff --git a/mdl/executor/cmd_layouts.go b/mdl/executor/cmd_layouts.go index 79ce5d25..4b6cee56 100644 --- a/mdl/executor/cmd_layouts.go +++ b/mdl/executor/cmd_layouts.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -64,6 +63,3 @@ func showLayouts(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -func (e *Executor) showLayouts(moduleName string) error { - return showLayouts(e.newExecContext(context.Background()), moduleName) -} diff --git a/mdl/executor/cmd_lint.go b/mdl/executor/cmd_lint.go index 8e556c21..704ee641 100644 --- a/mdl/executor/cmd_lint.go +++ b/mdl/executor/cmd_lint.go @@ -157,10 +157,3 @@ func showLintRules(ctx *ExecContext) error { // --- Executor method wrappers for backward compatibility --- -func (e *Executor) execLint(s *ast.LintStmt) error { - return execLint(e.newExecContext(context.Background()), s) -} - -func (e *Executor) showLintRules() error { - return showLintRules(e.newExecContext(context.Background())) -} diff --git a/mdl/executor/cmd_mermaid.go b/mdl/executor/cmd_mermaid.go index ba72749c..3febd039 100644 --- a/mdl/executor/cmd_mermaid.go +++ b/mdl/executor/cmd_mermaid.go @@ -395,7 +395,7 @@ func pageToMermaid(ctx *ExecContext, name ast.QualifiedName) error { } // Use raw widget data (same approach as describePage) - rawWidgets := e.getPageWidgetsFromRaw(foundPage.ID) + rawWidgets := getPageWidgetsFromRaw(ctx, foundPage.ID) var sb strings.Builder sb.WriteString("block-beta\n") diff --git a/mdl/executor/cmd_microflows_create.go b/mdl/executor/cmd_microflows_create.go index 983f7def..369321f6 100644 --- a/mdl/executor/cmd_microflows_create.go +++ b/mdl/executor/cmd_microflows_create.go @@ -63,7 +63,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { return mdlerrors.NewBackend("check existing microflows", err) } for _, existing := range existingMicroflows { - if existing.Name == s.Name.Name && e.getModuleID(existing.ContainerID) == module.ID { + if existing.Name == s.Name.Name && getModuleID(ctx, existing.ContainerID) == module.ID { if !s.CreateOrModify { return mdlerrors.NewAlreadyExistsMsg("microflow", s.Name.Module+"."+s.Name.Name, "microflow '"+s.Name.Module+"."+s.Name.Name+"' already exists (use CREATE OR REPLACE to overwrite)") } @@ -137,7 +137,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { } // Validate enumeration references for Enumeration types if p.Type.Kind == ast.TypeEnumeration && p.Type.EnumRef != nil { - if found := e.findEnumeration(p.Type.EnumRef.Module, p.Type.EnumRef.Name); found == nil { + if found := findEnumeration(ctx, p.Type.EnumRef.Module, p.Type.EnumRef.Name); found == nil { return mdlerrors.NewNotFoundMsg("enumeration", p.Type.EnumRef.Module+"."+p.Type.EnumRef.Name, fmt.Sprintf("enumeration '%s.%s' not found for parameter '%s'", p.Type.EnumRef.Module, p.Type.EnumRef.Name, p.Name)) } @@ -167,7 +167,7 @@ func execCreateMicroflow(ctx *ExecContext, s *ast.CreateMicroflowStmt) error { } // Validate enumeration references for return type if s.ReturnType.Type.Kind == ast.TypeEnumeration && s.ReturnType.Type.EnumRef != nil { - if found := e.findEnumeration(s.ReturnType.Type.EnumRef.Module, s.ReturnType.Type.EnumRef.Name); found == nil { + if found := findEnumeration(ctx, s.ReturnType.Type.EnumRef.Module, s.ReturnType.Type.EnumRef.Name); found == nil { return mdlerrors.NewNotFoundMsg("enumeration", s.ReturnType.Type.EnumRef.Module+"."+s.ReturnType.Type.EnumRef.Name, fmt.Sprintf("enumeration '%s.%s' not found for return type", s.ReturnType.Type.EnumRef.Module, s.ReturnType.Type.EnumRef.Name)) } diff --git a/mdl/executor/cmd_microflows_show.go b/mdl/executor/cmd_microflows_show.go index 4a9db700..1fe1a503 100644 --- a/mdl/executor/cmd_microflows_show.go +++ b/mdl/executor/cmd_microflows_show.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -814,26 +813,3 @@ func collectReachableNodes( // --- Executor method wrappers for callers in unmigrated code --- -func (e *Executor) showMicroflows(moduleName string) error { - return showMicroflows(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) showNanoflows(moduleName string) error { - return showNanoflows(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeMicroflow(name ast.QualifiedName) error { - return describeMicroflow(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeNanoflow(name ast.QualifiedName) error { - return describeNanoflow(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { - return describeMicroflowToString(e.newExecContext(context.Background()), name) -} - -func (e *Executor) renderMicroflowMDL(mf *microflows.Microflow, name ast.QualifiedName, entityNames map[model.ID]string, microflowNames map[model.ID]string, sourceMap map[string]elkSourceRange) string { - return renderMicroflowMDL(e.newExecContext(context.Background()), mf, name, entityNames, microflowNames, sourceMap) -} diff --git a/mdl/executor/cmd_misc.go b/mdl/executor/cmd_misc.go index 66aa4baf..f55fb6d4 100644 --- a/mdl/executor/cmd_misc.go +++ b/mdl/executor/cmd_misc.go @@ -4,7 +4,6 @@ package executor import ( - "context" "errors" "fmt" "os" @@ -33,21 +32,11 @@ func execUpdate(ctx *ExecContext) error { return execConnect(ctx, &ast.ConnectStmt{Path: path}) } -// Executor wrapper for unmigrated callers. -func (e *Executor) execUpdate() error { - return execUpdate(e.newExecContext(context.Background())) -} - // execRefresh handles REFRESH statements (alias for UPDATE). func execRefresh(ctx *ExecContext) error { return execUpdate(ctx) } -// Executor wrapper for unmigrated callers. -func (e *Executor) execRefresh() error { - return execRefresh(e.newExecContext(context.Background())) -} - // execSet handles SET statements. func execSet(ctx *ExecContext, s *ast.SetStmt) error { e := ctx.executor @@ -56,11 +45,6 @@ func execSet(ctx *ExecContext, s *ast.SetStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execSet(s *ast.SetStmt) error { - return execSet(e.newExecContext(context.Background()), s) -} - // execHelp handles HELP statements. func execHelp(ctx *ExecContext) error { help := `MDL Commands: @@ -339,11 +323,6 @@ Statement Terminator: return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execHelp() error { - return execHelp(e.newExecContext(context.Background())) -} - // showVersion displays Mendix project version information. func showVersion(ctx *ExecContext) error { e := ctx.executor @@ -361,11 +340,6 @@ func showVersion(ctx *ExecContext) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) showVersion() error { - return showVersion(e.newExecContext(context.Background())) -} - // execExit handles EXIT statements. // Note: This just signals exit intent via ErrExit. The actual cleanup // is done by the caller (CLI/REPL) when they handle ErrExit at the top level. @@ -375,11 +349,6 @@ func execExit(ctx *ExecContext) error { return ErrExit } -// Executor wrapper for unmigrated callers. -func (e *Executor) execExit() error { - return execExit(e.newExecContext(context.Background())) -} - // execExecuteScript handles EXECUTE SCRIPT statements. func execExecuteScript(ctx *ExecContext, s *ast.ExecuteScriptStmt) error { e := ctx.executor @@ -429,11 +398,6 @@ func execExecuteScript(ctx *ExecContext, s *ast.ExecuteScriptStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execExecuteScript(s *ast.ExecuteScriptStmt) error { - return execExecuteScript(e.newExecContext(context.Background()), s) -} - // stripSlashSeparators removes lines that contain only "/" from the script content. // This allows "/" to be used as a statement separator (SQL*Plus style) without // requiring the grammar to support it. diff --git a/mdl/executor/cmd_modules.go b/mdl/executor/cmd_modules.go index 78188184..ca87bae8 100644 --- a/mdl/executor/cmd_modules.go +++ b/mdl/executor/cmd_modules.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -45,7 +44,7 @@ func execCreateModule(ctx *ExecContext, s *ast.CreateModuleStmt) error { } // Invalidate cache so new module is visible - e.invalidateModuleCache() + invalidateModuleCache(ctx) fmt.Fprintf(ctx.Output, "Created module: %s\n", s.Name) return nil @@ -338,10 +337,6 @@ func execDropModule(ctx *ExecContext, s *ast.DropModuleStmt) error { } // Executor method wrapper — kept during migration for callers not yet -// converted to free functions (helpers.go). Remove once all callers are migrated. -func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { - return execCreateModule(e.newExecContext(context.Background()), s) -} // getModuleContainers returns a set of all container IDs that belong to a module // (including nested folders). @@ -383,11 +378,6 @@ func getModuleContainers(ctx *ExecContext, moduleID model.ID) map[model.ID]bool return containers } -// Executor method wrapper for getModuleContainers — kept during migration. -func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { - return getModuleContainers(e.newExecContext(context.Background()), moduleID) -} - // showModules handles SHOW MODULES command. func showModules(ctx *ExecContext) error { e := ctx.executor @@ -916,10 +906,3 @@ func sortEntitiesByGeneralization(entities []*domainmodel.Entity, moduleName str // Executor method wrappers for callers in unmigrated files. -func (e *Executor) showModules() error { - return showModules(e.newExecContext(context.Background())) -} - -func (e *Executor) describeModule(moduleName string, withAll bool) error { - return describeModule(e.newExecContext(context.Background()), moduleName, withAll) -} diff --git a/mdl/executor/cmd_move.go b/mdl/executor/cmd_move.go index de0d421d..76ecdcb3 100644 --- a/mdl/executor/cmd_move.go +++ b/mdl/executor/cmd_move.go @@ -304,7 +304,7 @@ func moveEntity(ctx *ExecContext, name ast.QualifiedName, sourceModule, targetMo // For cross-module moves, updates all EnumerationAttributeType references across all domain models. func moveEnumeration(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID, targetModuleName string) error { e := ctx.executor - enum := e.findEnumeration(name.Module, name.Name) + enum := findEnumeration(ctx, name.Module, name.Name) if enum == nil { return mdlerrors.NewNotFound("enumeration", name.String()) } diff --git a/mdl/executor/cmd_navigation.go b/mdl/executor/cmd_navigation.go index 5e1994a4..5fedf117 100644 --- a/mdl/executor/cmd_navigation.go +++ b/mdl/executor/cmd_navigation.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "io" "strings" @@ -74,11 +73,6 @@ func execAlterNavigation(ctx *ExecContext, s *ast.AlterNavigationStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execAlterNavigation(s *ast.AlterNavigationStmt) error { - return execAlterNavigation(e.newExecContext(context.Background()), s) -} - // convertMenuItemDef converts an AST NavMenuItemDef to a writer NavMenuItemSpec. func convertMenuItemDef(def ast.NavMenuItemDef) mpr.NavMenuItemSpec { spec := mpr.NavMenuItemSpec{ @@ -164,11 +158,6 @@ func showNavigation(ctx *ExecContext) error { return writeResult(ctx, result) } -// Executor wrapper for unmigrated callers. -func (e *Executor) showNavigation() error { - return showNavigation(e.newExecContext(context.Background())) -} - // showNavigationMenu handles SHOW NAVIGATION MENU [profile] command. // Displays the menu tree for a specific profile, or all profiles if none specified. func showNavigationMenu(ctx *ExecContext, profileName *ast.QualifiedName) error { @@ -195,11 +184,6 @@ func showNavigationMenu(ctx *ExecContext, profileName *ast.QualifiedName) error return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) showNavigationMenu(profileName *ast.QualifiedName) error { - return showNavigationMenu(e.newExecContext(context.Background()), profileName) -} - // showNavigationHomes handles SHOW NAVIGATION HOMES command. // Displays all home page configurations including role-based overrides. func showNavigationHomes(ctx *ExecContext) error { @@ -243,11 +227,6 @@ func showNavigationHomes(ctx *ExecContext) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) showNavigationHomes() error { - return showNavigationHomes(e.newExecContext(context.Background())) -} - // describeNavigation handles DESCRIBE NAVIGATION [profile] command. // Outputs a complete MDL-style description of a navigation profile. func describeNavigation(ctx *ExecContext, name ast.QualifiedName) error { @@ -276,11 +255,6 @@ func describeNavigation(ctx *ExecContext, name ast.QualifiedName) error { return mdlerrors.NewNotFound("navigation profile", name.Name) } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeNavigation(name ast.QualifiedName) error { - return describeNavigation(e.newExecContext(context.Background()), name) -} - // outputNavigationProfile outputs a single profile in round-trippable CREATE OR REPLACE NAVIGATION format. func outputNavigationProfile(ctx *ExecContext, p *mpr.NavigationProfile) { fmt.Fprintf(ctx.Output, "-- NAVIGATION PROFILE: %s\n", p.Name) diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index ac0bb78d..553e05c8 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -3,7 +3,6 @@ package executor import ( - "context" "crypto/sha256" "fmt" "io" @@ -1465,38 +1464,3 @@ func fetchODataMetadata(metadataUrl string) (metadata string, hash string, err e // Executor wrappers for unmigrated callers. -func (e *Executor) showODataClients(moduleName string) error { - return showODataClients(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeODataClient(name ast.QualifiedName) error { - return describeODataClient(e.newExecContext(context.Background()), name) -} - -func (e *Executor) outputConsumedODataServiceMDL(svc *model.ConsumedODataService, moduleName string, folderPath string) error { - return outputConsumedODataServiceMDL(e.newExecContext(context.Background()), svc, moduleName, folderPath) -} - -func (e *Executor) showODataServices(moduleName string) error { - return showODataServices(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeODataService(name ast.QualifiedName) error { - return describeODataService(e.newExecContext(context.Background()), name) -} - -func (e *Executor) outputPublishedODataServiceMDL(svc *model.PublishedODataService, moduleName string, folderPath string) error { - return outputPublishedODataServiceMDL(e.newExecContext(context.Background()), svc, moduleName, folderPath) -} - -func (e *Executor) showExternalEntities(moduleName string) error { - return showExternalEntities(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) showExternalActions(moduleName string) error { - return showExternalActions(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeExternalEntity(name ast.QualifiedName) error { - return describeExternalEntity(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_oql_plan.go b/mdl/executor/cmd_oql_plan.go index b6f696d8..ad5ba43d 100644 --- a/mdl/executor/cmd_oql_plan.go +++ b/mdl/executor/cmd_oql_plan.go @@ -3,7 +3,6 @@ package executor import ( - "context" "encoding/json" "fmt" "regexp" @@ -478,6 +477,3 @@ func buildScalarSubqueryTable(sel ast.OQLSelectItem, selectIndex, tableOffset in // --- Executor method wrapper for backward compatibility --- -func (e *Executor) OqlQueryPlanELK(qualifiedName string, entity *domainmodel.Entity) error { - return OqlQueryPlanELK(e.newExecContext(context.Background()), qualifiedName, entity) -} diff --git a/mdl/executor/cmd_pages_builder.go b/mdl/executor/cmd_pages_builder.go index 2c06f718..a88f869e 100644 --- a/mdl/executor/cmd_pages_builder.go +++ b/mdl/executor/cmd_pages_builder.go @@ -388,10 +388,6 @@ func execDropSnippet(ctx *ExecContext, s *ast.DropSnippetStmt) error { return mdlerrors.NewNotFound("snippet", s.Name.String()) } -func (e *Executor) getModuleID(containerID model.ID) model.ID { - return getModuleID(e.newExecContext(context.Background()), containerID) -} - func (e *Executor) getModuleName(moduleID model.ID) string { return getModuleName(e.newExecContext(context.Background()), moduleID) } diff --git a/mdl/executor/cmd_pages_create_v3.go b/mdl/executor/cmd_pages_create_v3.go index afc85f60..d515ec2e 100644 --- a/mdl/executor/cmd_pages_create_v3.go +++ b/mdl/executor/cmd_pages_create_v3.go @@ -23,7 +23,7 @@ func execCreatePageV3(ctx *ExecContext, s *ast.CreatePageStmtV3) error { // Version pre-check: page parameters require 11.0+ if len(s.Parameters) > 0 { - if err := e.checkFeature("pages", "page_parameters", + if err := checkFeature(ctx, "pages", "page_parameters", "CREATE PAGE with parameters", "pass data via a non-persistent entity or microflow parameter instead"); err != nil { return err diff --git a/mdl/executor/cmd_pages_describe.go b/mdl/executor/cmd_pages_describe.go index ce54a521..364b79fe 100644 --- a/mdl/executor/cmd_pages_describe.go +++ b/mdl/executor/cmd_pages_describe.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strconv" "strings" @@ -762,22 +761,3 @@ func pageParamTypeMDL(p *pages.PageParameter) string { return string(p.EntityID) } -func (e *Executor) describeLayout(name ast.QualifiedName) error { - return describeLayout(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describePage(name ast.QualifiedName) error { - return describePage(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeSnippet(name ast.QualifiedName) error { - return describeSnippet(e.newExecContext(context.Background()), name) -} - -func (e *Executor) getPageWidgetsFromRaw(pageID model.ID) []rawWidget { - return getPageWidgetsFromRaw(e.newExecContext(context.Background()), pageID) -} - -func (e *Executor) getSnippetWidgetsFromRaw(snippetID model.ID) []rawWidget { - return getSnippetWidgetsFromRaw(e.newExecContext(context.Background()), snippetID) -} diff --git a/mdl/executor/cmd_pages_show.go b/mdl/executor/cmd_pages_show.go index 23912d75..0a99c844 100644 --- a/mdl/executor/cmd_pages_show.go +++ b/mdl/executor/cmd_pages_show.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -77,6 +76,3 @@ func showPages(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -func (e *Executor) showPages(moduleName string) error { - return showPages(e.newExecContext(context.Background()), moduleName) -} diff --git a/mdl/executor/cmd_published_rest.go b/mdl/executor/cmd_published_rest.go index 989128e9..a4054de8 100644 --- a/mdl/executor/cmd_published_rest.go +++ b/mdl/executor/cmd_published_rest.go @@ -3,7 +3,6 @@ package executor import ( - "context" "errors" "fmt" "sort" @@ -190,7 +189,7 @@ func execCreatePublishedRestService(ctx *ExecContext, s *ast.CreatePublishedRest return mdlerrors.NewNotConnectedWrite() } - if err := e.checkFeature("integration", "published_rest_service", + if err := checkFeature(ctx, "integration", "published_rest_service", "CREATE PUBLISHED REST SERVICE", "upgrade your project to 10.0+"); err != nil { return err @@ -318,7 +317,7 @@ func execAlterPublishedRestService(ctx *ExecContext, s *ast.AlterPublishedRestSe return mdlerrors.NewNotConnectedWrite() } - if err := e.checkFeature("integration", "published_rest_alter", + if err := checkFeature(ctx, "integration", "published_rest_alter", "ALTER PUBLISHED REST SERVICE", "upgrade your project to 10.0+"); err != nil { return err @@ -384,10 +383,3 @@ func execAlterPublishedRestService(ctx *ExecContext, s *ast.AlterPublishedRestSe // Executor wrappers for unmigrated callers. -func (e *Executor) showPublishedRestServices(moduleName string) error { - return showPublishedRestServices(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describePublishedRestService(name ast.QualifiedName) error { - return describePublishedRestService(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_rest_clients.go b/mdl/executor/cmd_rest_clients.go index 7c75ee9f..4288313e 100644 --- a/mdl/executor/cmd_rest_clients.go +++ b/mdl/executor/cmd_rest_clients.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "io" "sort" @@ -289,7 +288,7 @@ func createRestClient(ctx *ExecContext, stmt *ast.CreateRestClientStmt) error { } // Version pre-check: REST clients require 10.1+ - if err := e.checkFeature("integration", "rest_client_basic", + if err := checkFeature(ctx, "integration", "rest_client_basic", "CREATE REST CLIENT", "upgrade your project to 10.1+"); err != nil { return err @@ -504,10 +503,3 @@ func formatRestAuthValue(value string) string { // Executor wrappers for unmigrated callers. -func (e *Executor) showRestClients(moduleName string) error { - return showRestClients(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeRestClient(name ast.QualifiedName) error { - return describeRestClient(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_search.go b/mdl/executor/cmd_search.go index 40fbf12c..024bb223 100644 --- a/mdl/executor/cmd_search.go +++ b/mdl/executor/cmd_search.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strings" @@ -231,18 +230,3 @@ func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { // --- Executor method wrappers for backward compatibility --- -func (e *Executor) execShowCallers(s *ast.ShowStmt) error { - return execShowCallers(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execShowCallees(s *ast.ShowStmt) error { - return execShowCallees(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execShowReferences(s *ast.ShowStmt) error { - return execShowReferences(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execShowImpact(s *ast.ShowStmt) error { - return execShowImpact(e.newExecContext(context.Background()), s) -} diff --git a/mdl/executor/cmd_security.go b/mdl/executor/cmd_security.go index e53d0e22..594aff90 100644 --- a/mdl/executor/cmd_security.go +++ b/mdl/executor/cmd_security.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" @@ -765,50 +764,3 @@ func describeUserRole(ctx *ExecContext, name ast.QualifiedName) error { // Executor method wrappers — delegate to free functions for callers that // still use the Executor receiver (e.g. executor_query.go). -func (e *Executor) showProjectSecurity() error { - return showProjectSecurity(e.newExecContext(context.Background())) -} - -func (e *Executor) showModuleRoles(moduleName string) error { - return showModuleRoles(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) showUserRoles() error { - return showUserRoles(e.newExecContext(context.Background())) -} - -func (e *Executor) showDemoUsers() error { - return showDemoUsers(e.newExecContext(context.Background())) -} - -func (e *Executor) showAccessOnEntity(name *ast.QualifiedName) error { - return showAccessOnEntity(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showAccessOnMicroflow(name *ast.QualifiedName) error { - return showAccessOnMicroflow(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error { - return showAccessOnPage(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error { - return showAccessOnWorkflow(e.newExecContext(context.Background()), name) -} - -func (e *Executor) showSecurityMatrix(moduleName string) error { - return showSecurityMatrix(e.newExecContext(context.Background()), moduleName) -} - -func (e *Executor) describeModuleRole(name ast.QualifiedName) error { - return describeModuleRole(e.newExecContext(context.Background()), name) -} - -func (e *Executor) describeDemoUser(userName string) error { - return describeDemoUser(e.newExecContext(context.Background()), userName) -} - -func (e *Executor) describeUserRole(name ast.QualifiedName) error { - return describeUserRole(e.newExecContext(context.Background()), name) -} diff --git a/mdl/executor/cmd_security_write.go b/mdl/executor/cmd_security_write.go index f6409cfb..aa8756f4 100644 --- a/mdl/executor/cmd_security_write.go +++ b/mdl/executor/cmd_security_write.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" @@ -1126,7 +1125,7 @@ func execGrantPublishedRestServiceAccess(ctx *ExecContext, s *ast.GrantPublished return mdlerrors.NewNotConnectedWrite() } - if err := e.checkFeature("integration", "published_rest_grant_revoke", + if err := checkFeature(ctx, "integration", "published_rest_grant_revoke", "GRANT ACCESS ON PUBLISHED REST SERVICE", "upgrade your project to 10.0+"); err != nil { return err @@ -1286,86 +1285,3 @@ func execUpdateSecurity(ctx *ExecContext, s *ast.UpdateSecurityStmt) error { // Executor method wrappers — delegate to free functions for callers that // still use the Executor receiver (e.g. executor_query.go). -func (e *Executor) execCreateModuleRole(s *ast.CreateModuleRoleStmt) error { - return execCreateModuleRole(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error { - return execDropModuleRole(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error { - return execCreateUserRole(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execAlterUserRole(s *ast.AlterUserRoleStmt) error { - return execAlterUserRole(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDropUserRole(s *ast.DropUserRoleStmt) error { - return execDropUserRole(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantEntityAccess(s *ast.GrantEntityAccessStmt) error { - return execGrantEntityAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokeEntityAccess(s *ast.RevokeEntityAccessStmt) error { - return execRevokeEntityAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantMicroflowAccess(s *ast.GrantMicroflowAccessStmt) error { - return execGrantMicroflowAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokeMicroflowAccess(s *ast.RevokeMicroflowAccessStmt) error { - return execRevokeMicroflowAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantPageAccess(s *ast.GrantPageAccessStmt) error { - return execGrantPageAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error { - return execRevokePageAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantWorkflowAccess(s *ast.GrantWorkflowAccessStmt) error { - return execGrantWorkflowAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokeWorkflowAccess(s *ast.RevokeWorkflowAccessStmt) error { - return execRevokeWorkflowAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execAlterProjectSecurity(s *ast.AlterProjectSecurityStmt) error { - return execAlterProjectSecurity(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execCreateDemoUser(s *ast.CreateDemoUserStmt) error { - return execCreateDemoUser(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDropDemoUser(s *ast.DropDemoUserStmt) error { - return execDropDemoUser(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantODataServiceAccess(s *ast.GrantODataServiceAccessStmt) error { - return execGrantODataServiceAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokeODataServiceAccess(s *ast.RevokeODataServiceAccessStmt) error { - return execRevokeODataServiceAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execGrantPublishedRestServiceAccess(s *ast.GrantPublishedRestServiceAccessStmt) error { - return execGrantPublishedRestServiceAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execRevokePublishedRestServiceAccess(s *ast.RevokePublishedRestServiceAccessStmt) error { - return execRevokePublishedRestServiceAccess(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execUpdateSecurity(s *ast.UpdateSecurityStmt) error { - return execUpdateSecurity(e.newExecContext(context.Background()), s) -} diff --git a/mdl/executor/cmd_settings.go b/mdl/executor/cmd_settings.go index 5f5ce9bf..4a2d2b1d 100644 --- a/mdl/executor/cmd_settings.go +++ b/mdl/executor/cmd_settings.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "strconv" "strings" @@ -84,11 +83,6 @@ func showSettings(ctx *ExecContext) error { return writeResult(ctx, tr) } -// Executor wrapper for unmigrated callers. -func (e *Executor) showSettings() error { - return showSettings(e.newExecContext(context.Background())) -} - // describeSettings outputs the full MDL description of all settings. func describeSettings(ctx *ExecContext) error { e := ctx.executor @@ -175,11 +169,6 @@ func describeSettings(ctx *ExecContext) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeSettings() error { - return describeSettings(e.newExecContext(context.Background())) -} - // alterSettings modifies project settings based on ALTER SETTINGS statement. func alterSettings(ctx *ExecContext, stmt *ast.AlterSettingsStmt) error { e := ctx.executor @@ -281,11 +270,6 @@ func alterSettings(ctx *ExecContext, stmt *ast.AlterSettingsStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) alterSettings(stmt *ast.AlterSettingsStmt) error { - return alterSettings(e.newExecContext(context.Background()), stmt) -} - func alterSettingsConfiguration(ctx *ExecContext, ps *model.ProjectSettings, stmt *ast.AlterSettingsStmt) error { e := ctx.executor if ps.Configuration == nil { @@ -481,11 +465,6 @@ func createConfiguration(ctx *ExecContext, stmt *ast.CreateConfigurationStmt) er return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) createConfiguration(stmt *ast.CreateConfigurationStmt) error { - return createConfiguration(e.newExecContext(context.Background()), stmt) -} - // dropConfiguration handles DROP CONFIGURATION 'name'. func dropConfiguration(ctx *ExecContext, stmt *ast.DropConfigurationStmt) error { e := ctx.executor @@ -519,11 +498,6 @@ func dropConfiguration(ctx *ExecContext, stmt *ast.DropConfigurationStmt) error return mdlerrors.NewNotFound("configuration", stmt.Name) } -// Executor wrapper for unmigrated callers. -func (e *Executor) dropConfiguration(stmt *ast.DropConfigurationStmt) error { - return dropConfiguration(e.newExecContext(context.Background()), stmt) -} - // settingsValueToString converts an AST settings value to string. func settingsValueToString(val any) string { switch v := val.(type) { diff --git a/mdl/executor/cmd_snippets.go b/mdl/executor/cmd_snippets.go index bd9cb995..6044e2e2 100644 --- a/mdl/executor/cmd_snippets.go +++ b/mdl/executor/cmd_snippets.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -63,6 +62,3 @@ func showSnippets(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -func (e *Executor) showSnippets(moduleName string) error { - return showSnippets(e.newExecContext(context.Background()), moduleName) -} diff --git a/mdl/executor/cmd_sql.go b/mdl/executor/cmd_sql.go index 390bb62b..c091c046 100644 --- a/mdl/executor/cmd_sql.go +++ b/mdl/executor/cmd_sql.go @@ -272,38 +272,3 @@ func execSQLDescribeTable(ctx *ExecContext, s *ast.SQLDescribeTableStmt) error { // Executor wrappers for unmigrated callers. -func (e *Executor) execSQLConnect(s *ast.SQLConnectStmt) error { - return execSQLConnect(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLDisconnect(s *ast.SQLDisconnectStmt) error { - return execSQLDisconnect(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLConnections() error { - return execSQLConnections(e.newExecContext(context.Background())) -} - -func (e *Executor) execSQLQuery(s *ast.SQLQueryStmt) error { - return execSQLQuery(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLShowTables(s *ast.SQLShowTablesStmt) error { - return execSQLShowTables(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLShowViews(s *ast.SQLShowViewsStmt) error { - return execSQLShowViews(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLShowFunctions(s *ast.SQLShowFunctionsStmt) error { - return execSQLShowFunctions(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLGenerateConnector(s *ast.SQLGenerateConnectorStmt) error { - return execSQLGenerateConnector(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execSQLDescribeTable(s *ast.SQLDescribeTableStmt) error { - return execSQLDescribeTable(e.newExecContext(context.Background()), s) -} diff --git a/mdl/executor/cmd_structure.go b/mdl/executor/cmd_structure.go index 864f2d7d..8d2466f7 100644 --- a/mdl/executor/cmd_structure.go +++ b/mdl/executor/cmd_structure.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -63,11 +62,6 @@ func execShowStructure(ctx *ExecContext, s *ast.ShowStmt) error { } } -// Executor method wrapper for unmigrated callers. -func (e *Executor) execShowStructure(s *ast.ShowStmt) error { - return execShowStructure(e.newExecContext(context.Background()), s) -} - // structureDepth1JSON emits structure as a JSON table with one row per module // and columns for each element type count. func structureDepth1JSON(ctx *ExecContext, modules []structureModule) error { diff --git a/mdl/executor/cmd_styling.go b/mdl/executor/cmd_styling.go index 5bd7047e..560387ee 100644 --- a/mdl/executor/cmd_styling.go +++ b/mdl/executor/cmd_styling.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "path/filepath" "reflect" @@ -72,11 +71,6 @@ func execShowDesignProperties(ctx *ExecContext, s *ast.ShowDesignPropertiesStmt) return nil } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) error { - return execShowDesignProperties(e.newExecContext(context.Background()), s) -} - // printDesignProperties prints properties for a widget type, showing inherited "Widget" props separately. func printDesignProperties(ctx *ExecContext, registry *ThemeRegistry, dpKey string) { // Print inherited Widget properties @@ -151,7 +145,7 @@ func execDescribeStyling(ctx *ExecContext, s *ast.DescribeStylingStmt) error { if foundPage == nil { return mdlerrors.NewNotFound("page", s.ContainerName.String()) } - rawWidgets = e.getPageWidgetsFromRaw(foundPage.ID) + rawWidgets = getPageWidgetsFromRaw(ctx, foundPage.ID) } else if s.ContainerType == "SNIPPET" { // Find snippet allSnippets, err := e.reader.ListSnippets() @@ -171,7 +165,7 @@ func execDescribeStyling(ctx *ExecContext, s *ast.DescribeStylingStmt) error { if foundSnippet == nil { return mdlerrors.NewNotFound("snippet", s.ContainerName.String()) } - rawWidgets = e.getSnippetWidgetsFromRaw(foundSnippet.ID) + rawWidgets = getSnippetWidgetsFromRaw(ctx, foundSnippet.ID) } if len(rawWidgets) == 0 { @@ -222,11 +216,6 @@ func execDescribeStyling(ctx *ExecContext, s *ast.DescribeStylingStmt) error { return nil } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execDescribeStyling(s *ast.DescribeStylingStmt) error { - return execDescribeStyling(e.newExecContext(context.Background()), s) -} - // collectStyledWidgets walks rawWidget tree and collects widgets that have styling. // If widgetName is set, only returns the widget matching that name. func collectStyledWidgets(widgets []rawWidget, widgetName string) []rawWidget { @@ -291,11 +280,6 @@ func execAlterStyling(ctx *ExecContext, s *ast.AlterStylingStmt) error { return mdlerrors.NewUnsupported("unsupported container type: " + s.ContainerType) } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execAlterStyling(s *ast.AlterStylingStmt) error { - return execAlterStyling(e.newExecContext(context.Background()), s) -} - func alterStylingOnPage(ctx *ExecContext, s *ast.AlterStylingStmt, h *ContainerHierarchy) error { e := ctx.executor @@ -552,11 +536,6 @@ func findPageByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHierar return nil, mdlerrors.NewNotFound("page", name.String()) } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) findPageByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Page, error) { - return findPageByName(e.newExecContext(context.Background()), name, h) -} - // findSnippetByName looks up a snippet by qualified name. func findSnippetByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHierarchy) (*pages.Snippet, model.ID, error) { e := ctx.executor @@ -575,7 +554,3 @@ func findSnippetByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHie return nil, "", mdlerrors.NewNotFound("snippet", name.String()) } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) findSnippetByName(name ast.QualifiedName, h *ContainerHierarchy) (*pages.Snippet, model.ID, error) { - return findSnippetByName(e.newExecContext(context.Background()), name, h) -} diff --git a/mdl/executor/cmd_widgets.go b/mdl/executor/cmd_widgets.go index fc51829c..0710af9c 100644 --- a/mdl/executor/cmd_widgets.go +++ b/mdl/executor/cmd_widgets.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" @@ -80,11 +79,6 @@ func execShowWidgets(ctx *ExecContext, s *ast.ShowWidgetsStmt) error { return nil } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execShowWidgets(s *ast.ShowWidgetsStmt) error { - return execShowWidgets(e.newExecContext(context.Background()), s) -} - // execUpdateWidgets handles the UPDATE WIDGETS statement. func execUpdateWidgets(ctx *ExecContext, s *ast.UpdateWidgetsStmt) error { e := ctx.executor @@ -145,11 +139,6 @@ func execUpdateWidgets(ctx *ExecContext, s *ast.UpdateWidgetsStmt) error { return nil } -// Wrapper for callers that haven't been migrated yet. -func (e *Executor) execUpdateWidgets(s *ast.UpdateWidgetsStmt) error { - return execUpdateWidgets(e.newExecContext(context.Background()), s) -} - // widgetRef holds information about a widget to be updated. type widgetRef struct { ID string diff --git a/mdl/executor/cmd_workflows.go b/mdl/executor/cmd_workflows.go index 0d95a7bd..fc0c391b 100644 --- a/mdl/executor/cmd_workflows.go +++ b/mdl/executor/cmd_workflows.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "sort" "strings" @@ -71,11 +70,6 @@ func showWorkflows(ctx *ExecContext, moduleName string) error { return writeResult(ctx, result) } -// Executor wrapper for unmigrated callers. -func (e *Executor) showWorkflows(moduleName string) error { - return showWorkflows(e.newExecContext(context.Background()), moduleName) -} - // countWorkflowActivities counts total activities, user tasks, and decisions in a workflow. func countWorkflowActivities(wf *workflows.Workflow) (total, userTasks, decisions int) { if wf.Flow == nil { @@ -147,11 +141,6 @@ func describeWorkflow(ctx *ExecContext, name ast.QualifiedName) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeWorkflow(name ast.QualifiedName) error { - return describeWorkflow(e.newExecContext(context.Background()), name) -} - // describeWorkflowToString generates MDL-like output for a workflow and returns it as a string. func describeWorkflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { e := ctx.executor @@ -247,11 +236,6 @@ func describeWorkflowToString(ctx *ExecContext, name ast.QualifiedName) (string, return strings.Join(lines, "\n"), nil, nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) describeWorkflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) { - return describeWorkflowToString(e.newExecContext(context.Background()), name) -} - // formatAnnotation returns an ANNOTATION statement for a workflow activity annotation. // The annotation is emitted as a parseable MDL statement so it survives round-trips. func formatAnnotation(annotation string, indent string) string { diff --git a/mdl/executor/cmd_workflows_write.go b/mdl/executor/cmd_workflows_write.go index 034ea02e..c0015e04 100644 --- a/mdl/executor/cmd_workflows_write.go +++ b/mdl/executor/cmd_workflows_write.go @@ -4,7 +4,6 @@ package executor import ( - "context" "fmt" "strings" "unicode" @@ -129,11 +128,6 @@ func execCreateWorkflow(ctx *ExecContext, s *ast.CreateWorkflowStmt) error { return nil } -// Executor wrapper for unmigrated callers. -func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { - return execCreateWorkflow(e.newExecContext(context.Background()), s) -} - // execDropWorkflow handles DROP WORKFLOW statements. func execDropWorkflow(ctx *ExecContext, s *ast.DropWorkflowStmt) error { e := ctx.executor @@ -167,11 +161,6 @@ func execDropWorkflow(ctx *ExecContext, s *ast.DropWorkflowStmt) error { return mdlerrors.NewNotFound("workflow", s.Name.Module+"."+s.Name.Name) } -// Executor wrapper for unmigrated callers. -func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { - return execDropWorkflow(e.newExecContext(context.Background()), s) -} - // generateWorkflowUUID generates a UUID for workflow elements. func generateWorkflowUUID() string { return mpr.GenerateID() diff --git a/mdl/executor/executor_connect.go b/mdl/executor/executor_connect.go index f79fc314..4c3a178d 100644 --- a/mdl/executor/executor_connect.go +++ b/mdl/executor/executor_connect.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "github.com/mendixlabs/mxcli/mdl/ast" @@ -87,18 +86,6 @@ func execDisconnect(ctx *ExecContext) error { // Executor method wrappers — kept during migration for callers not yet // converted to free functions. Remove once all callers are migrated. -func (e *Executor) execConnect(s *ast.ConnectStmt) error { - return execConnect(e.newExecContext(context.Background()), s) -} - -func (e *Executor) execDisconnect() error { - return execDisconnect(e.newExecContext(context.Background())) -} - -func (e *Executor) reconnect() error { - return reconnect(e.newExecContext(context.Background())) -} - func execStatus(ctx *ExecContext) error { e := ctx.executor if e.writer == nil { diff --git a/mdl/executor/format.go b/mdl/executor/format.go index a9fbb9f5..98269be7 100644 --- a/mdl/executor/format.go +++ b/mdl/executor/format.go @@ -153,14 +153,6 @@ func (e *Executor) writeResult(r *TableResult) error { return writeResult(e.newExecContext(context.Background()), r) } -func (e *Executor) writeResultTable(r *TableResult) { - writeResultTable(e.newExecContext(context.Background()), r) -} - -func (e *Executor) writeResultJSON(r *TableResult) error { - return writeResultJSON(e.newExecContext(context.Background()), r) -} - func (e *Executor) writeDescribeJSON(name, objectType string, fn func() error) error { return writeDescribeJSON(e.newExecContext(context.Background()), name, objectType, fn) } diff --git a/mdl/executor/helpers.go b/mdl/executor/helpers.go index 25b5040e..e1465111 100644 --- a/mdl/executor/helpers.go +++ b/mdl/executor/helpers.go @@ -5,7 +5,6 @@ package executor import ( - "context" "fmt" "strings" @@ -78,7 +77,7 @@ func findOrCreateModule(ctx *ExecContext, name string) (*model.Module, error) { return nil, err } // Auto-create the module - if createErr := e.execCreateModule(&ast.CreateModuleStmt{Name: name}); createErr != nil { + if createErr := execCreateModule(ctx, &ast.CreateModuleStmt{Name: name}); createErr != nil { return nil, mdlerrors.NewBackend("auto-create module "+name, createErr) } return findModule(ctx, name) @@ -491,66 +490,6 @@ func buildJavaActionQualifiedNames(ctx *ExecContext) map[string]bool { // Executor method wrappers (for callers in unmigrated files) // ---------------------------------------------------------------------------- -func (e *Executor) getModulesFromCache() ([]*model.Module, error) { - return getModulesFromCache(e.newExecContext(context.Background())) -} - -func (e *Executor) invalidateModuleCache() { - invalidateModuleCache(e.newExecContext(context.Background())) -} - -func (e *Executor) findModule(name string) (*model.Module, error) { - return findModule(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findOrCreateModule(name string) (*model.Module, error) { - return findOrCreateModule(e.newExecContext(context.Background()), name) -} - -func (e *Executor) findModuleByID(id model.ID) (*model.Module, error) { - return findModuleByID(e.newExecContext(context.Background()), id) -} - -func (e *Executor) resolveFolder(moduleID model.ID, folderPath string) (model.ID, error) { - return resolveFolder(e.newExecContext(context.Background()), moduleID, folderPath) -} - -func (e *Executor) createFolder(name string, containerID model.ID) (model.ID, error) { - return createFolder(e.newExecContext(context.Background()), name, containerID) -} - -func (e *Executor) enumerationExists(qualifiedName string) bool { - return enumerationExists(e.newExecContext(context.Background()), qualifiedName) -} - -func (e *Executor) validateWidgetReferences(widgets []*ast.WidgetV3, sc *scriptContext) []string { - return validateWidgetReferences(e.newExecContext(context.Background()), widgets, sc) -} - -func (e *Executor) buildMicroflowQualifiedNames() map[string]bool { - return buildMicroflowQualifiedNames(e.newExecContext(context.Background())) -} - -func (e *Executor) buildNanoflowQualifiedNames() map[string]bool { - return buildNanoflowQualifiedNames(e.newExecContext(context.Background())) -} - -func (e *Executor) buildPageQualifiedNames() map[string]bool { - return buildPageQualifiedNames(e.newExecContext(context.Background())) -} - -func (e *Executor) buildSnippetQualifiedNames() map[string]bool { - return buildSnippetQualifiedNames(e.newExecContext(context.Background())) -} - -func (e *Executor) buildEntityQualifiedNames() map[string]bool { - return buildEntityQualifiedNames(e.newExecContext(context.Background())) -} - -func (e *Executor) buildJavaActionQualifiedNames() map[string]bool { - return buildJavaActionQualifiedNames(e.newExecContext(context.Background())) -} - // ---------------------------------------------------------------------------- // Data Type Conversion // ---------------------------------------------------------------------------- diff --git a/mdl/executor/hierarchy.go b/mdl/executor/hierarchy.go index 2906864f..a534e79c 100644 --- a/mdl/executor/hierarchy.go +++ b/mdl/executor/hierarchy.go @@ -156,10 +156,3 @@ func (e *Executor) getHierarchy() (*ContainerHierarchy, error) { return getHierarchy(e.newExecContext(context.Background())) } -func (e *Executor) invalidateHierarchy() { - invalidateHierarchy(e.newExecContext(context.Background())) -} - -func (e *Executor) invalidateDomainModelsCache() { - invalidateDomainModelsCache(e.newExecContext(context.Background())) -} diff --git a/mdl/executor/oql_type_inference.go b/mdl/executor/oql_type_inference.go index d7da8474..4a04e502 100644 --- a/mdl/executor/oql_type_inference.go +++ b/mdl/executor/oql_type_inference.go @@ -3,7 +3,6 @@ package executor import ( - "context" "fmt" "regexp" "strings" @@ -73,11 +72,6 @@ func inferOQLTypes(ctx *ExecContext, oqlQuery string, declaredAttrs []ast.ViewAt return columns, warnings } -// InferOQLTypes analyzes an OQL query and returns the expected types for each column. -func (e *Executor) InferOQLTypes(oqlQuery string, declaredAttrs []ast.ViewAttribute) ([]OQLColumnInfo, []string) { - return inferOQLTypes(e.newExecContext(context.Background()), oqlQuery, declaredAttrs) -} - // extractAliasMap parses the FROM clause to build a map of alias -> qualified entity name. func extractAliasMap(oql string) map[string]string { aliasMap := make(map[string]string) @@ -332,11 +326,6 @@ func validateViewEntityTypes(ctx *ExecContext, stmt *ast.CreateViewEntityStmt) [ return errors } -// ValidateViewEntityTypes validates that declared attribute types match inferred OQL types. -func (e *Executor) ValidateViewEntityTypes(stmt *ast.CreateViewEntityStmt) []string { - return validateViewEntityTypes(e.newExecContext(context.Background()), stmt) -} - // extractSelectClause extracts the SELECT clause from an OQL query. // Handles subqueries by tracking parenthesis depth to find the main FROM clause. func extractSelectClause(oql string) string { From cdd03cac040d8883be0d7278a53955bf3396d32b Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 18:14:21 +0200 Subject: [PATCH 15/16] refactor: add TODO for ExecContext.Fork() in captureDescribe --- mdl/executor/cmd_catalog.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mdl/executor/cmd_catalog.go b/mdl/executor/cmd_catalog.go index 685783c3..af2b1f3c 100644 --- a/mdl/executor/cmd_catalog.go +++ b/mdl/executor/cmd_catalog.go @@ -691,7 +691,8 @@ func captureDescribeParallel(ctx *ExecContext, objectType string, qualifiedName } qn := ast.QualifiedName{Module: parts[0], Name: parts[1]} - // Create a goroutine-local executor: shared reader + cache, own output buffer + // Create a goroutine-local executor: shared reader + cache, own output buffer. + // TODO: Replace with ExecContext.Fork() when MR 3 makes ExecContext self-contained. var buf bytes.Buffer local := &Executor{ reader: ctx.executor.reader, From 98bd89904899a054cbe21db40455f00fc52df19b Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Fri, 17 Apr 2026 18:53:58 +0200 Subject: [PATCH 16/16] fix: address PR #225 review feedback --- mdl/executor/cmd_agenteditor_agents.go | 1 - mdl/executor/cmd_agenteditor_kbs.go | 1 - mdl/executor/cmd_agenteditor_mcpservices.go | 1 - mdl/executor/cmd_agenteditor_models.go | 1 - mdl/executor/cmd_associations.go | 1 - mdl/executor/cmd_businessevents.go | 1 - mdl/executor/cmd_context.go | 1 - mdl/executor/cmd_contract.go | 1 - mdl/executor/cmd_datatransformer.go | 1 - mdl/executor/cmd_dbconnection.go | 1 - mdl/executor/cmd_domainmodel_elk.go | 1 - mdl/executor/cmd_entities.go | 1 - mdl/executor/cmd_entities_access.go | 1 - mdl/executor/cmd_entities_describe.go | 1 - mdl/executor/cmd_enumerations.go | 1 + mdl/executor/cmd_imagecollections.go | 1 - mdl/executor/cmd_import.go | 2 +- mdl/executor/cmd_languages.go | 1 - mdl/executor/cmd_layouts.go | 1 - mdl/executor/cmd_lint.go | 1 - mdl/executor/cmd_microflows_show.go | 1 - mdl/executor/cmd_modules.go | 1 - mdl/executor/cmd_odata.go | 1 - mdl/executor/cmd_oql_plan.go | 1 - mdl/executor/cmd_pages_describe.go | 1 - mdl/executor/cmd_pages_show.go | 1 - mdl/executor/cmd_published_rest.go | 1 - mdl/executor/cmd_rest_clients.go | 1 - mdl/executor/cmd_search.go | 1 - mdl/executor/cmd_security.go | 1 - mdl/executor/cmd_security_write.go | 1 - mdl/executor/cmd_snippets.go | 1 - mdl/executor/cmd_sql.go | 1 - mdl/executor/cmd_styling.go | 1 - mdl/executor/hierarchy.go | 1 - mdl/executor/oql_type_inference.go | 2 +- 36 files changed, 3 insertions(+), 35 deletions(-) diff --git a/mdl/executor/cmd_agenteditor_agents.go b/mdl/executor/cmd_agenteditor_agents.go index 15d2e2fc..af051ebb 100644 --- a/mdl/executor/cmd_agenteditor_agents.go +++ b/mdl/executor/cmd_agenteditor_agents.go @@ -252,4 +252,3 @@ func findAgentEditorAgent(ctx *ExecContext, moduleName, agentName string) *agent } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_agenteditor_kbs.go b/mdl/executor/cmd_agenteditor_kbs.go index 2c0bda45..7258ee20 100644 --- a/mdl/executor/cmd_agenteditor_kbs.go +++ b/mdl/executor/cmd_agenteditor_kbs.go @@ -148,4 +148,3 @@ func findAgentEditorKnowledgeBase(ctx *ExecContext, moduleName, kbName string) * } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_agenteditor_mcpservices.go b/mdl/executor/cmd_agenteditor_mcpservices.go index 24c961a9..f22bfbda 100644 --- a/mdl/executor/cmd_agenteditor_mcpservices.go +++ b/mdl/executor/cmd_agenteditor_mcpservices.go @@ -132,4 +132,3 @@ func findAgentEditorConsumedMCPService(ctx *ExecContext, moduleName, svcName str } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index 4747913c..956359d7 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -154,4 +154,3 @@ func findAgentEditorModel(ctx *ExecContext, moduleName, modelName string) *agent } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_associations.go b/mdl/executor/cmd_associations.go index d6a16be5..614603ad 100644 --- a/mdl/executor/cmd_associations.go +++ b/mdl/executor/cmd_associations.go @@ -478,4 +478,3 @@ func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { } // --- Executor method wrappers for callers not yet migrated --- - diff --git a/mdl/executor/cmd_businessevents.go b/mdl/executor/cmd_businessevents.go index b42d3bdb..f2555208 100644 --- a/mdl/executor/cmd_businessevents.go +++ b/mdl/executor/cmd_businessevents.go @@ -422,4 +422,3 @@ func generateChannelName() string { } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_context.go b/mdl/executor/cmd_context.go index 34cc254c..0ddb428a 100644 --- a/mdl/executor/cmd_context.go +++ b/mdl/executor/cmd_context.go @@ -608,4 +608,3 @@ func assembleODataServiceContext(ctx *ExecContext, out *strings.Builder, name st } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_contract.go b/mdl/executor/cmd_contract.go index 6a5a048c..aec5a48f 100644 --- a/mdl/executor/cmd_contract.go +++ b/mdl/executor/cmd_contract.go @@ -1430,4 +1430,3 @@ func asyncTypeString(p *mpr.AsyncAPIProperty) string { } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_datatransformer.go b/mdl/executor/cmd_datatransformer.go index 27d6be45..b497ca68 100644 --- a/mdl/executor/cmd_datatransformer.go +++ b/mdl/executor/cmd_datatransformer.go @@ -186,4 +186,3 @@ func execDropDataTransformer(ctx *ExecContext, s *ast.DropDataTransformerStmt) e } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_dbconnection.go b/mdl/executor/cmd_dbconnection.go index 66189392..2e0d3c44 100644 --- a/mdl/executor/cmd_dbconnection.go +++ b/mdl/executor/cmd_dbconnection.go @@ -338,4 +338,3 @@ func dbTypeToMDLType(bsonType string) string { } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_domainmodel_elk.go b/mdl/executor/cmd_domainmodel_elk.go index 9d235643..c1abb80a 100644 --- a/mdl/executor/cmd_domainmodel_elk.go +++ b/mdl/executor/cmd_domainmodel_elk.go @@ -516,4 +516,3 @@ func classifyEntity(entity *domainmodel.Entity) string { func (e *Executor) DomainModelELK(name string) error { return domainModelELK(e.newExecContext(context.Background()), name) } - diff --git a/mdl/executor/cmd_entities.go b/mdl/executor/cmd_entities.go index 717a60b7..ffd37705 100644 --- a/mdl/executor/cmd_entities.go +++ b/mdl/executor/cmd_entities.go @@ -984,4 +984,3 @@ func warnEntityReferences(ctx *ExecContext, entityName string) { } // --- Executor method wrappers for callers not yet migrated --- - diff --git a/mdl/executor/cmd_entities_access.go b/mdl/executor/cmd_entities_access.go index 4d0d5b24..3b113e84 100644 --- a/mdl/executor/cmd_entities_access.go +++ b/mdl/executor/cmd_entities_access.go @@ -216,4 +216,3 @@ func formatAccessRuleResult(ctx *ExecContext, moduleName, entityName string, rol } // --- Executor method wrappers for callers not yet migrated --- - diff --git a/mdl/executor/cmd_entities_describe.go b/mdl/executor/cmd_entities_describe.go index 99bdfdde..dab7dd4f 100644 --- a/mdl/executor/cmd_entities_describe.go +++ b/mdl/executor/cmd_entities_describe.go @@ -529,4 +529,3 @@ func lookupMicroflowName(ctx *ExecContext, mfID model.ID) string { } // --- Executor method wrappers for callers not yet migrated --- - diff --git a/mdl/executor/cmd_enumerations.go b/mdl/executor/cmd_enumerations.go index 7bc3bc08..a458773a 100644 --- a/mdl/executor/cmd_enumerations.go +++ b/mdl/executor/cmd_enumerations.go @@ -258,6 +258,7 @@ var mendixReservedWords = map[string]bool{ "true": true, "try": true, "void": true, "volatile": true, "while": true, // Mendix-specific reserved identifiers + "changedby": true, "changeddate": true, "con": true, "context": true, "createddate": true, "currentuser": true, "guid": true, "id": true, "mendixobject": true, "submetaobjectname": true, } diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index ca6d524e..97537064 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -256,4 +256,3 @@ func findImageCollection(ctx *ExecContext, moduleName, collectionName string) *m } return nil } - diff --git a/mdl/executor/cmd_import.go b/mdl/executor/cmd_import.go index 18a60fbd..c7b71e7e 100644 --- a/mdl/executor/cmd_import.go +++ b/mdl/executor/cmd_import.go @@ -49,7 +49,7 @@ func execImport(ctx *ExecContext, s *ast.ImportStmt) error { } // Resolve association LINK mappings - goCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + goCtx, cancel := context.WithTimeout(ctx.Context, 10*time.Minute) defer cancel() assocs, err := resolveImportLinks(ctx, goCtx, targetConn, s) diff --git a/mdl/executor/cmd_languages.go b/mdl/executor/cmd_languages.go index ed6b52b0..86b67393 100644 --- a/mdl/executor/cmd_languages.go +++ b/mdl/executor/cmd_languages.go @@ -50,4 +50,3 @@ func showLanguages(ctx *ExecContext) error { } // --- Executor method wrapper for backward compatibility --- - diff --git a/mdl/executor/cmd_layouts.go b/mdl/executor/cmd_layouts.go index 4b6cee56..af2b8aa5 100644 --- a/mdl/executor/cmd_layouts.go +++ b/mdl/executor/cmd_layouts.go @@ -62,4 +62,3 @@ func showLayouts(ctx *ExecContext, moduleName string) error { } return writeResult(ctx, result) } - diff --git a/mdl/executor/cmd_lint.go b/mdl/executor/cmd_lint.go index 704ee641..137f2585 100644 --- a/mdl/executor/cmd_lint.go +++ b/mdl/executor/cmd_lint.go @@ -156,4 +156,3 @@ func showLintRules(ctx *ExecContext) error { } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_microflows_show.go b/mdl/executor/cmd_microflows_show.go index 1fe1a503..f2bd58d7 100644 --- a/mdl/executor/cmd_microflows_show.go +++ b/mdl/executor/cmd_microflows_show.go @@ -812,4 +812,3 @@ func collectReachableNodes( } // --- Executor method wrappers for callers in unmigrated code --- - diff --git a/mdl/executor/cmd_modules.go b/mdl/executor/cmd_modules.go index ca87bae8..ffcedb4b 100644 --- a/mdl/executor/cmd_modules.go +++ b/mdl/executor/cmd_modules.go @@ -905,4 +905,3 @@ func sortEntitiesByGeneralization(entities []*domainmodel.Entity, moduleName str } // Executor method wrappers for callers in unmigrated files. - diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index 553e05c8..2e945685 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -1463,4 +1463,3 @@ func fetchODataMetadata(metadataUrl string) (metadata string, hash string, err e } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_oql_plan.go b/mdl/executor/cmd_oql_plan.go index ad5ba43d..3a6f18cf 100644 --- a/mdl/executor/cmd_oql_plan.go +++ b/mdl/executor/cmd_oql_plan.go @@ -476,4 +476,3 @@ func buildScalarSubqueryTable(sel ast.OQLSelectItem, selectIndex, tableOffset in } // --- Executor method wrapper for backward compatibility --- - diff --git a/mdl/executor/cmd_pages_describe.go b/mdl/executor/cmd_pages_describe.go index 364b79fe..ed0fd762 100644 --- a/mdl/executor/cmd_pages_describe.go +++ b/mdl/executor/cmd_pages_describe.go @@ -760,4 +760,3 @@ func pageParamTypeMDL(p *pages.PageParameter) string { } return string(p.EntityID) } - diff --git a/mdl/executor/cmd_pages_show.go b/mdl/executor/cmd_pages_show.go index 0a99c844..cb56a1f1 100644 --- a/mdl/executor/cmd_pages_show.go +++ b/mdl/executor/cmd_pages_show.go @@ -75,4 +75,3 @@ func showPages(ctx *ExecContext, moduleName string) error { } return writeResult(ctx, result) } - diff --git a/mdl/executor/cmd_published_rest.go b/mdl/executor/cmd_published_rest.go index a4054de8..4e94df21 100644 --- a/mdl/executor/cmd_published_rest.go +++ b/mdl/executor/cmd_published_rest.go @@ -382,4 +382,3 @@ func execAlterPublishedRestService(ctx *ExecContext, s *ast.AlterPublishedRestSe } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_rest_clients.go b/mdl/executor/cmd_rest_clients.go index 4288313e..22e62cf9 100644 --- a/mdl/executor/cmd_rest_clients.go +++ b/mdl/executor/cmd_rest_clients.go @@ -502,4 +502,3 @@ func formatRestAuthValue(value string) string { } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_search.go b/mdl/executor/cmd_search.go index 024bb223..7b5f2b3f 100644 --- a/mdl/executor/cmd_search.go +++ b/mdl/executor/cmd_search.go @@ -229,4 +229,3 @@ func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { } // --- Executor method wrappers for backward compatibility --- - diff --git a/mdl/executor/cmd_security.go b/mdl/executor/cmd_security.go index 594aff90..96f7d4a9 100644 --- a/mdl/executor/cmd_security.go +++ b/mdl/executor/cmd_security.go @@ -763,4 +763,3 @@ func describeUserRole(ctx *ExecContext, name ast.QualifiedName) error { // Executor method wrappers — delegate to free functions for callers that // still use the Executor receiver (e.g. executor_query.go). - diff --git a/mdl/executor/cmd_security_write.go b/mdl/executor/cmd_security_write.go index aa8756f4..10fb0755 100644 --- a/mdl/executor/cmd_security_write.go +++ b/mdl/executor/cmd_security_write.go @@ -1284,4 +1284,3 @@ func execUpdateSecurity(ctx *ExecContext, s *ast.UpdateSecurityStmt) error { // Executor method wrappers — delegate to free functions for callers that // still use the Executor receiver (e.g. executor_query.go). - diff --git a/mdl/executor/cmd_snippets.go b/mdl/executor/cmd_snippets.go index 6044e2e2..e16148c7 100644 --- a/mdl/executor/cmd_snippets.go +++ b/mdl/executor/cmd_snippets.go @@ -61,4 +61,3 @@ func showSnippets(ctx *ExecContext, moduleName string) error { } return writeResult(ctx, result) } - diff --git a/mdl/executor/cmd_sql.go b/mdl/executor/cmd_sql.go index c091c046..42a0e4d4 100644 --- a/mdl/executor/cmd_sql.go +++ b/mdl/executor/cmd_sql.go @@ -271,4 +271,3 @@ func execSQLDescribeTable(ctx *ExecContext, s *ast.SQLDescribeTableStmt) error { } // Executor wrappers for unmigrated callers. - diff --git a/mdl/executor/cmd_styling.go b/mdl/executor/cmd_styling.go index 560387ee..48ecd04e 100644 --- a/mdl/executor/cmd_styling.go +++ b/mdl/executor/cmd_styling.go @@ -553,4 +553,3 @@ func findSnippetByName(ctx *ExecContext, name ast.QualifiedName, h *ContainerHie } return nil, "", mdlerrors.NewNotFound("snippet", name.String()) } - diff --git a/mdl/executor/hierarchy.go b/mdl/executor/hierarchy.go index a534e79c..cde6bee3 100644 --- a/mdl/executor/hierarchy.go +++ b/mdl/executor/hierarchy.go @@ -155,4 +155,3 @@ func invalidateDomainModelsCache(ctx *ExecContext) { func (e *Executor) getHierarchy() (*ContainerHierarchy, error) { return getHierarchy(e.newExecContext(context.Background())) } - diff --git a/mdl/executor/oql_type_inference.go b/mdl/executor/oql_type_inference.go index 4a04e502..d0279e98 100644 --- a/mdl/executor/oql_type_inference.go +++ b/mdl/executor/oql_type_inference.go @@ -656,7 +656,7 @@ func inferAttributeType(ctx *ExecContext, attrPath string, col *OQLColumnInfo) a return ast.DataType{Kind: ast.TypeUnknown} } -// oqlFindEntity looks up an entity by module and name. +// findEntity looks up an entity by module and name. func findEntity(ctx *ExecContext, moduleName, entityName string) (*domainmodel.Entity, error) { e := ctx.executor // Get all entities