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 2b7bd47c..af051ebb 100644 --- a/mdl/executor/cmd_agenteditor_agents.go +++ b/mdl/executor/cmd_agenteditor_agents.go @@ -17,7 +17,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() } @@ -27,7 +28,7 @@ func (e *Executor) showAgentEditorAgents(moduleName string) error { return mdlerrors.NewBackend("list agents", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -58,22 +59,23 @@ func (e *Executor) showAgentEditorAgents(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 // 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()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -82,10 +84,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,107 +136,108 @@ 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -247,3 +250,5 @@ func (e *Executor) findAgentEditorAgent(moduleName, agentName string) *agentedit } return nil } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_agenteditor_kbs.go b/mdl/executor/cmd_agenteditor_kbs.go index 16a82597..7258ee20 100644 --- a/mdl/executor/cmd_agenteditor_kbs.go +++ b/mdl/executor/cmd_agenteditor_kbs.go @@ -17,7 +17,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() } @@ -27,7 +28,7 @@ func (e *Executor) showAgentEditorKnowledgeBases(moduleName string) error { return mdlerrors.NewBackend("list knowledge bases", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -57,21 +58,22 @@ func (e *Executor) showAgentEditorKnowledgeBases(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. -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()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -80,10 +82,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,24 +115,25 @@ 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -143,3 +146,5 @@ func (e *Executor) findAgentEditorKnowledgeBase(moduleName, kbName string) *agen } return nil } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_agenteditor_mcpservices.go b/mdl/executor/cmd_agenteditor_mcpservices.go index 966fd94e..f22bfbda 100644 --- a/mdl/executor/cmd_agenteditor_mcpservices.go +++ b/mdl/executor/cmd_agenteditor_mcpservices.go @@ -17,7 +17,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() } @@ -27,7 +28,7 @@ func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { return mdlerrors.NewBackend("list consumed MCP services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -53,21 +54,22 @@ func (e *Executor) showAgentEditorConsumedMCPServices(moduleName string) error { } 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. -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()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -76,10 +78,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,24 +99,25 @@ 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -127,3 +130,5 @@ func (e *Executor) findAgentEditorConsumedMCPService(moduleName, svcName string) } return nil } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index b7e877e8..956359d7 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -17,7 +17,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() } @@ -27,7 +28,7 @@ func (e *Executor) showAgentEditorModels(moduleName string) error { return mdlerrors.NewBackend("list models", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -59,22 +60,23 @@ func (e *Executor) showAgentEditorModels(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. // 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()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -83,10 +85,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,24 +121,25 @@ 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -149,3 +152,5 @@ func (e *Executor) findAgentEditorModel(moduleName, modelName string) *agentedit } return nil } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_alter_page.go b/mdl/executor/cmd_alter_page.go index 9a247af3..be1b79bc 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() } @@ -24,7 +25,7 @@ func (e *Executor) execAlterPage(s *ast.AlterPageStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -37,14 +38,14 @@ func (e *Executor) execAlterPage(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 } @@ -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_alter_workflow.go b/mdl/executor/cmd_alter_workflow.go index 2c4ddd95..f73f183b 100644 --- a/mdl/executor/cmd_alter_workflow.go +++ b/mdl/executor/cmd_alter_workflow.go @@ -20,7 +20,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() } @@ -29,13 +30,13 @@ func (e *Executor) execAlterWorkflow(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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -81,7 +82,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 +90,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 +102,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 +110,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 +118,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: @@ -140,8 +141,8 @@ func (e *Executor) execAlterWorkflow(s *ast.AlterWorkflowStmt) error { return mdlerrors.NewBackend("save modified workflow", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered workflow %s.%s\n", s.Name.Module, s.Name.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Altered workflow %s.%s\n", s.Name.Module, s.Name.Name) return nil } @@ -483,9 +484,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 +507,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 +519,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 +559,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 +570,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 +594,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 +608,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 +660,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 +672,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 +719,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 +754,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 +820,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 +845,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_associations.go b/mdl/executor/cmd_associations.go index b005b836..614603ad 100644 --- a/mdl/executor/cmd_associations.go +++ b/mdl/executor/cmd_associations.go @@ -15,13 +15,14 @@ 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() } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -36,7 +37,7 @@ func (e *Executor) execCreateAssociation(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()) } @@ -46,7 +47,7 @@ func (e *Executor) execCreateAssociation(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()) } @@ -126,29 +127,30 @@ func (e *Executor) execCreateAssociation(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). 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() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -176,7 +178,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 +201,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,13 +210,14 @@ 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() } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -229,7 +232,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 +241,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 +250,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 { @@ -329,16 +333,17 @@ func (e *Executor) showAssociations(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. -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") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -350,20 +355,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,8 +377,9 @@ func (e *Executor) showAssociation(name *ast.QualifiedName) error { } // describeAssociation handles DESCRIBE ASSOCIATION command. -func (e *Executor) describeAssociation(name ast.QualifiedName) error { - module, err := e.findModule(name.Module) +func describeAssociation(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -389,7 +395,7 @@ func (e *Executor) describeAssociation(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) } @@ -406,17 +412,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 +436,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 +445,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 +463,18 @@ 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 --- diff --git a/mdl/executor/cmd_businessevents.go b/mdl/executor/cmd_businessevents.go index 9844d33a..f2555208 100644 --- a/mdl/executor/cmd_businessevents.go +++ b/mdl/executor/cmd_businessevents.go @@ -13,7 +13,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() } @@ -23,7 +25,7 @@ func (e *Executor) showBusinessEventServices(inModule string) error { return mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -40,9 +42,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 } @@ -83,17 +85,19 @@ func (e *Executor) showBusinessEventServices(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. -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() } @@ -103,7 +107,7 @@ func (e *Executor) showBusinessEvents(inModule string) error { return mdlerrors.NewBackend("list business event services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -152,9 +156,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 } @@ -166,11 +170,13 @@ func (e *Executor) showBusinessEvents(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. -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() } @@ -181,7 +187,7 @@ func (e *Executor) describeBusinessEventService(name ast.QualifiedName) error { } // Use hierarchy to resolve container IDs to module names - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -205,21 +211,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,32 +254,34 @@ 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() } 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) } @@ -296,7 +304,7 @@ func (e *Executor) createBusinessEventService(stmt *ast.CreateBusinessEventServi // 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) } @@ -369,12 +377,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() } @@ -384,7 +394,7 @@ func (e *Executor) dropBusinessEventService(stmt *ast.DropBusinessEventServiceSt 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) } @@ -396,7 +406,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 +420,5 @@ func generateChannelName() string { uuid := mpr.GenerateID() return strings.ReplaceAll(uuid, "-", "") } + +// Executor wrappers for unmigrated callers. diff --git a/mdl/executor/cmd_catalog.go b/mdl/executor/cmd_catalog.go index a2d8f44b..af2b1f3c 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,38 +681,44 @@ 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) } 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: 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 +729,51 @@ 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) } } // 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 +784,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 +827,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 +856,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 +875,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 +891,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 +906,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 +914,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 b414333a..dbfabbb4 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,14 +14,16 @@ 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) } // 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) } @@ -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 } @@ -76,18 +79,20 @@ func (e *Executor) showConstants(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) } // 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) } // Use hierarchy for proper module resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -97,7 +102,7 @@ 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) } } @@ -105,37 +110,42 @@ func (e *Executor) describeConstant(name ast.QualifiedName) error { } // 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 { // 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() + h, _ := getHierarchy(ctx) 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 +257,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() } @@ -258,7 +270,7 @@ func (e *Executor) createConstant(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 } @@ -275,7 +287,7 @@ func (e *Executor) createConstant(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) @@ -293,8 +305,8 @@ func (e *Executor) createConstant(stmt *ast.CreateConstantStmt) error { if err := e.writer.UpdateConstant(c); err != nil { return mdlerrors.NewBackend("update constant", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified constant: %s.%s\n", modName, c.Name) + invalidateHierarchy(ctx) + 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)) @@ -310,7 +322,7 @@ func (e *Executor) createConstant(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) } @@ -329,14 +341,16 @@ func (e *Executor) createConstant(stmt *ast.CreateConstantStmt) error { if err := e.writer.CreateConstant(constant); err != nil { return mdlerrors.NewBackend("create constant", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created constant: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + invalidateHierarchy(ctx) + 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) @@ -347,7 +361,7 @@ func (e *Executor) showConstantValues(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) } @@ -373,7 +387,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 } @@ -426,11 +440,13 @@ func (e *Executor) showConstantValues(moduleName string) error { } result.Rows = append(result.Rows, []any{r.constant, r.configuration, val}) } - return e.writeResult(result) + return writeResult(ctx, result) } // 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() } @@ -441,7 +457,7 @@ func (e *Executor) dropConstant(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) } @@ -454,8 +470,8 @@ func (e *Executor) dropConstant(stmt *ast.DropConstantStmt) error { if err := e.writer.DeleteConstant(c.ID); err != nil { return mdlerrors.NewBackend("drop constant", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped constant: %s.%s\n", modName, c.Name) + 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 73d56c6c..0ddb428a 100644 --- a/mdl/executor/cmd_context.go +++ b/mdl/executor/cmd_context.go @@ -12,13 +12,13 @@ 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 { 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) } @@ -29,7 +29,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 +40,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 +84,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 +95,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 +111,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 +128,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 +144,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 +167,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 +186,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 +211,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 +231,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 +246,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 +263,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 +286,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 +301,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 +316,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 +330,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 +344,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 +359,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 +373,9 @@ 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) { 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] @@ -393,16 +393,16 @@ func (e *Executor) assembleSnippetContext(out *strings.Builder, name string, dep 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") // 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 +416,7 @@ 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) { out.WriteString("### Java Action Definition\n\n```sql\n") parts := strings.SplitN(name, ".", 2) if len(parts) == 2 { @@ -424,16 +424,16 @@ func (e *Executor) assembleJavaActionContext(out *strings.Builder, name string) 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") // 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 +447,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 +462,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 +476,10 @@ 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) { // 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] @@ -505,16 +505,16 @@ func (e *Executor) assembleWorkflowContext(out *strings.Builder, name string, de 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") // 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 +531,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 +546,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 +561,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 +578,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 +594,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 +606,5 @@ func (e *Executor) assembleODataServiceContext(out *strings.Builder, name string out.WriteString("(none found)\n") } } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_contract.go b/mdl/executor/cmd_contract.go index c9ed342d..aec5a48f 100644 --- a/mdl/executor/cmd_contract.go +++ b/mdl/executor/cmd_contract.go @@ -15,12 +15,12 @@ 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 { 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 +56,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 } @@ -71,22 +71,22 @@ func (e *Executor) showContractEntities(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 (e *Executor) showContractActions(name *ast.QualifiedName) error { +func showContractActions(ctx *ExecContext, name *ast.QualifiedName) error { 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 } @@ -135,11 +135,11 @@ func (e *Executor) showContractActions(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]. -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 +147,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 +158,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 +184,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 +207,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 +215,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 +237,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 +279,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,23 +305,24 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, "", mdlerrors.NewBackend("build hierarchy", err) } @@ -445,12 +446,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 } @@ -475,7 +477,7 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err targetModule = s.ServiceRef.Module } - module, err := e.findModule(targetModule) + module, err := findModule(ctx, targetModule) if err != nil { return err } @@ -627,13 +629,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 +651,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 +664,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 +675,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 +698,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 +789,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 +810,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 +882,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 +1034,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 +1250,18 @@ 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 { 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 } @@ -1279,22 +1285,22 @@ func (e *Executor) showContractChannels(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 (e *Executor) showContractMessages(name *ast.QualifiedName) error { +func showContractMessages(ctx *ExecContext, name *ast.QualifiedName) error { 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 } @@ -1322,17 +1328,17 @@ func (e *Executor) showContractMessages(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. -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 +1348,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 +1373,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,13 +1384,14 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, "", mdlerrors.NewBackend("build hierarchy", err) } @@ -1421,3 +1428,5 @@ func asyncTypeString(p *mpr.AsyncAPIProperty) string { } return p.Type } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_datatransformer.go b/mdl/executor/cmd_datatransformer.go index 77c919cb..b497ca68 100644 --- a/mdl/executor/cmd_datatransformer.go +++ b/mdl/executor/cmd_datatransformer.go @@ -12,13 +12,15 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -42,7 +44,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 } @@ -51,17 +53,19 @@ func (e *Executor) listDataTransformers(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -73,7 +77,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,18 +107,20 @@ 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() } - 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 } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } @@ -138,14 +144,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() } @@ -155,7 +163,7 @@ func (e *Executor) execDropDataTransformer(s *ast.DropDataTransformerStmt) error return mdlerrors.NewBackend("list data transformers", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -168,7 +176,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 +184,5 @@ func (e *Executor) execDropDataTransformer(s *ast.DropDataTransformerStmt) error return mdlerrors.NewNotFound("data transformer", s.Name.Module+"."+s.Name.Name) } + +// Executor wrappers for unmigrated callers. diff --git a/mdl/executor/cmd_dbconnection.go b/mdl/executor/cmd_dbconnection.go index 429a9f56..2e0d3c44 100644 --- a/mdl/executor/cmd_dbconnection.go +++ b/mdl/executor/cmd_dbconnection.go @@ -13,7 +13,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() } @@ -22,14 +24,14 @@ func (e *Executor) createDatabaseConnection(stmt *ast.CreateDatabaseConnectionSt 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) @@ -53,7 +55,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{ @@ -115,19 +117,21 @@ func (e *Executor) createDatabaseConnection(stmt *ast.CreateDatabaseConnectionSt return mdlerrors.NewBackend("create database connection", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created database connection: %s.%s\n", stmt.Name.Module, stmt.Name.Name) + invalidateHierarchy(ctx) + 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -156,7 +160,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 } @@ -171,17 +175,19 @@ func (e *Executor) showDatabaseConnections(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -190,7 +196,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 +204,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,29 +260,31 @@ 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 "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } @@ -328,3 +336,5 @@ func dbTypeToMDLType(bsonType string) string { return "String" } } + +// Executor wrappers for unmigrated callers. diff --git a/mdl/executor/cmd_diff.go b/mdl/executor/cmd_diff.go index 87824a9b..f05bd4d0 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,49 +125,55 @@ 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 - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -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,14 +199,15 @@ 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) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -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,37 +231,38 @@ 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) { result := &DiffResult{ ObjectType: "Enumeration", ObjectName: s.Name, - Proposed: e.enumerationStmtToMDL(s), + Proposed: enumerationStmtToMDL(ctx, s), } // 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 } - h, _ := e.getHierarchy() + h, _ := getHierarchy(ctx) 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) + module, err := findModule(ctx, s.Name.Module) if err != nil { result.IsNew = true return result, nil @@ -266,7 +276,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,15 +286,16 @@ 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 - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { result.IsNew = true return result, nil @@ -304,10 +315,10 @@ func (e *Executor) diffMicroflow(s *ast.CreateMicroflowStmt) (*DiffResult, error 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 = e.compareMicroflows(result.Current, result.Proposed) + result.Changes = compareMicroflows(ctx, result.Current, result.Proposed) return result, nil } } @@ -321,7 +332,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 +340,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 +382,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 +412,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 +439,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 +464,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 +482,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 +500,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 +528,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..b698c8ad 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,7 @@ 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 { var raw map[string]any if err := bson.Unmarshal(content, &raw); err != nil { return fmt.Sprintf("-- Error parsing BSON: %v", err) @@ -275,7 +282,7 @@ func (e *Executor) bsonToMDL(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 != "" { @@ -286,23 +293,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 +318,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 +332,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 +363,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 +385,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 +401,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,12 +496,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 { qn := splitQualifiedName(qualifiedName) mf := mpr.ParseMicroflowFromRaw(raw, model.ID(qn.Name), "") - entityNames, microflowNames := e.buildNameLookups() - return e.renderMicroflowMDL(mf, qn, entityNames, microflowNames, nil) + entityNames, microflowNames := buildNameLookups(ctx) + return renderMicroflowMDL(ctx, mf, qn, entityNames, microflowNames, nil) } // splitQualifiedName parses "Module.Name" into an ast.QualifiedName. @@ -510,13 +517,14 @@ 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 { return entityNames, microflowNames } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return entityNames, microflowNames } @@ -537,7 +545,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 +571,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 +605,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 +636,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 +655,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 +674,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 +691,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_domainmodel_elk.go b/mdl/executor/cmd_domainmodel_elk.go index 9ae04c22..c1abb80a 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,20 +65,21 @@ 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 - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -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() } @@ -165,7 +168,7 @@ func (e *Executor) EntityFocusELK(qualifiedName string) error { } moduleName, entityName := parts[0], parts[1] - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -189,10 +192,10 @@ func (e *Executor) EntityFocusELK(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, _ := 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,10 +324,11 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return allEntityNames, allEntityModules } @@ -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,10 @@ 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) +} diff --git a/mdl/executor/cmd_entities.go b/mdl/executor/cmd_entities.go index 477b22bb..ffd37705 100644 --- a/mdl/executor/cmd_entities.go +++ b/mdl/executor/cmd_entities.go @@ -18,14 +18,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,13 +45,14 @@ 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() } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -149,7 +150,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 +242,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 } @@ -273,18 +274,18 @@ func (e *Executor) execCreateEntity(s *ast.CreateEntityStmt) error { return mdlerrors.NewBackend("update entity", err) } // Invalidate caches so updated entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified entity: %s\n", s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Modified entity: %s\n", s.Name) } else { // Create new entity if err := e.writer.CreateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("create entity", err) } // Invalidate caches so new entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Created entity: %s\n", s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Created entity: %s\n", s.Name) } e.trackModifiedDomainModel(module.ID, module.Name) @@ -292,13 +293,14 @@ 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() } // 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 @@ -318,7 +320,7 @@ func (e *Executor) execCreateViewEntity(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 } @@ -443,31 +445,32 @@ func (e *Executor) execCreateViewEntity(s *ast.CreateViewEntityStmt) error { return mdlerrors.NewBackend("update view entity", err) } // Invalidate caches so updated entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified view entity: %s\n", s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + 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 { return mdlerrors.NewBackend("create view entity", err) } // Invalidate caches so new entity is visible - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Created view entity: %s\n", s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + 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() } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -539,7 +542,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) } @@ -594,9 +597,9 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added attribute '%s' to entity %s\n", a.Name, s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Added attribute '%s' to entity %s\n", a.Name, s.Name) case ast.AlterEntityRenameAttribute: found := false @@ -613,9 +616,9 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("rename attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Renamed attribute '%s' to '%s' on entity %s\n", s.AttributeName, s.NewName, s.Name) + 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: // CALCULATED attributes are only supported on persistent entities @@ -631,7 +634,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) } @@ -650,9 +653,9 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("modify attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Modified attribute '%s' on entity %s\n", s.AttributeName, s.Name) + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) + 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 @@ -749,33 +752,33 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop attribute", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped attribute '%s' from entity %s\n", s.AttributeName, s.Name) + 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 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 if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("set documentation", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Set documentation on entity %s\n", s.Name) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Set documentation on entity %s\n", s.Name) case ast.AlterEntitySetComment: // Comments are stored as documentation in the Mendix model @@ -783,8 +786,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("set comment", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Set comment on entity %s\n", s.Name) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Set comment on entity %s\n", s.Name) case ast.AlterEntitySetPosition: if s.Position == nil { @@ -794,8 +797,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { 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) + 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: if s.Index == nil { @@ -828,8 +831,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add index", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added index to entity %s\n", s.Name) + invalidateDomainModelsCache(ctx) + 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) @@ -852,14 +855,14 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop index", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped index '%s' from entity %s\n", s.IndexName, s.Name) + invalidateDomainModelsCache(ctx) + 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 } @@ -875,8 +878,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("add event handler", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Added event handler %s %s on %s\n", + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Added event handler %s %s on %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) case ast.AlterEntityDropEventHandler: @@ -901,8 +904,8 @@ func (e *Executor) execAlterEntity(s *ast.AlterEntityStmt) error { if err := e.writer.UpdateEntity(dm.ID, entity); err != nil { return mdlerrors.NewBackend("drop event handler", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped event handler %s %s from %s\n", + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Dropped event handler %s %s from %s\n", s.EventHandler.Moment, s.EventHandler.Event, s.Name) default: @@ -914,13 +917,14 @@ 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() } // Find module and entity - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -933,7 +937,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" { @@ -944,8 +948,8 @@ func (e *Executor) execDropEntity(s *ast.DropEntityStmt) error { if err := e.writer.DeleteEntity(dm.ID, entity.ID); err != nil { return mdlerrors.NewBackend("delete entity", err) } - e.invalidateDomainModelsCache() - fmt.Fprintf(e.output, "Dropped entity: %s\n", s.Name) + invalidateDomainModelsCache(ctx) + fmt.Fprintf(ctx.Output, "Dropped entity: %s\n", s.Name) return nil } } @@ -955,7 +959,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 +974,13 @@ 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 --- diff --git a/mdl/executor/cmd_entities_access.go b/mdl/executor/cmd_entities_access.go index 9aa5df28..3b113e84 100644 --- a/mdl/executor/cmd_entities_access.go +++ b/mdl/executor/cmd_entities_access.go @@ -11,7 +11,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 +37,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 +50,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 +117,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 +141,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,10 +163,11 @@ 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 { - e.invalidateDomainModelsCache() +func formatAccessRuleResult(ctx *ExecContext, moduleName, entityName string, roleNames []string) string { + e := ctx.executor + invalidateDomainModelsCache(ctx) - module, err := e.findModule(moduleName) + module, err := findModule(ctx, moduleName) if err != nil { return "" } @@ -204,7 +205,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 +214,5 @@ func (e *Executor) formatAccessRuleResult(moduleName, entityName string, roleNam return " Result: (no access)\n" } + +// --- 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 0999df46..dab7dd4f 100644 --- a/mdl/executor/cmd_entities_describe.go +++ b/mdl/executor/cmd_entities_describe.go @@ -15,7 +15,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 { @@ -153,16 +154,17 @@ func (e *Executor) showEntities(moduleName string) error { } result.Rows = append(result.Rows, rowData) } - return e.writeResult(result) + return writeResult(ctx, result) } // 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") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -174,12 +176,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 +201,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,8 +216,9 @@ func (e *Executor) showEntity(name *ast.QualifiedName) error { } // describeEntity handles DESCRIBE ENTITY command. -func (e *Executor) describeEntity(name ast.QualifiedName) error { - module, err := e.findModule(name.Module) +func describeEntity(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -229,11 +232,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 +249,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 +317,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 +360,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 +392,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 +400,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 +421,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 +439,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 +464,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) @@ -482,7 +486,7 @@ func (e *Executor) resolveMicroflowByName(qualifiedName string) (model.ID, error return "", mdlerrors.NewBackend("list microflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "", mdlerrors.NewBackend("build hierarchy", err) } @@ -499,13 +503,14 @@ 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 "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return "" } @@ -522,3 +527,5 @@ func (e *Executor) lookupMicroflowName(mfID model.ID) string { } return "" } + +// --- Executor method wrappers for callers not yet migrated --- diff --git a/mdl/executor/cmd_enumerations.go b/mdl/executor/cmd_enumerations.go index 7760a1e9..a458773a 100644 --- a/mdl/executor/cmd_enumerations.go +++ b/mdl/executor/cmd_enumerations.go @@ -15,7 +15,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() } @@ -31,13 +33,13 @@ func (e *Executor) execCreateEnumeration(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 } // 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)) } @@ -73,20 +75,22 @@ func (e *Executor) execCreateEnumeration(s *ast.CreateEnumerationStmt) error { } // Invalidate hierarchy cache so the new enumeration's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) - 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil } @@ -102,13 +106,15 @@ func (e *Executor) findEnumeration(moduleName, enumName string) *model.Enumerati } // 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() } @@ -122,12 +128,12 @@ func (e *Executor) execDropEnumeration(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) } - fmt.Fprintf(e.output, "Dropped enumeration: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Dropped enumeration: %s\n", s.Name) return nil } } @@ -137,14 +143,16 @@ 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) } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -183,17 +191,19 @@ func (e *Executor) showEnumerations(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) } // 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -204,10 +214,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 +227,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 } } diff --git a/mdl/executor/cmd_export_mappings.go b/mdl/executor/cmd_export_mappings.go index 9f2958e5..7fc3419a 100644 --- a/mdl/executor/cmd_export_mappings.go +++ b/mdl/executor/cmd_export_mappings.go @@ -4,6 +4,7 @@ package executor import ( "fmt" + "io" "sort" "strings" @@ -14,7 +15,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() } @@ -24,7 +26,7 @@ func (e *Executor) showExportMappings(inModule string) error { return mdlerrors.NewBackend("list export mappings", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -57,9 +59,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 } @@ -73,11 +75,12 @@ func (e *Executor) showExportMappings(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) } // 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,40 +94,40 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return err } 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) { +func printExportMappingElement(w io.Writer, elem *model.ExportMappingElement, depth int, isRoot bool) { indent := strings.Repeat(" ", depth) if elem.Kind == "Object" { if isRoot { @@ -133,7 +136,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 +145,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,17 +173,18 @@ 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() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } @@ -222,8 +226,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 +363,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 +381,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_features.go b/mdl/executor/cmd_features.go index 2b804b8e..02a74fc7 100644 --- a/mdl/executor/cmd_features.go +++ b/mdl/executor/cmd_features.go @@ -14,7 +14,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 } @@ -47,7 +49,9 @@ func (e *Executor) checkFeature(area, name, statement, hint string) error { // 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 +67,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 +86,19 @@ 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 { +func showFeaturesAll(ctx *ExecContext, reg *versions.Registry, pv versions.SemVer) error { 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{ @@ -118,20 +122,20 @@ func (e *Executor) showFeaturesAll(reg *versions.Registry, pv versions.SemVer) e 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 (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 { 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"}, @@ -150,17 +154,17 @@ func (e *Executor) showFeaturesInArea(reg *versions.Registry, pv versions.SemVer } tr.Rows = append(tr.Rows, []any{f.DisplayName(), avail, fmt.Sprintf("%s", f.MinVersion), notes}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } -func (e *Executor) showFeaturesAddedSince(reg *versions.Registry, sinceV versions.SemVer) error { +func showFeaturesAddedSince(ctx *ExecContext, reg *versions.Registry, sinceV versions.SemVer) error { 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"}, @@ -176,5 +180,5 @@ func (e *Executor) showFeaturesAddedSince(reg *versions.Registry, sinceV version } 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 0b7c04b0..af2a630f 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,12 +50,13 @@ 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() } - module, err := e.findModule(s.Module) + module, err := findModule(ctx, s.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Module) } @@ -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) } @@ -74,19 +75,20 @@ func (e *Executor) execDropFolder(s *ast.DropFolderStmt) error { return mdlerrors.NewBackend(fmt.Sprintf("delete folder '%s'", s.FolderPath), err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped folder: '%s' in %s\n", s.FolderPath, s.Module) + invalidateHierarchy(ctx) + 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() } // 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) } @@ -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) } @@ -105,7 +107,7 @@ func (e *Executor) execMoveFolder(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) } @@ -116,7 +118,7 @@ func (e *Executor) execMoveFolder(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) } @@ -129,12 +131,12 @@ func (e *Executor) execMoveFolder(s *ast.MoveFolderStmt) error { return mdlerrors.NewBackend("move folder", err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) target := targetModule.Name 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..f822d9fa 100644 --- a/mdl/executor/cmd_fragments.go +++ b/mdl/executor/cmd_fragments.go @@ -14,67 +14,70 @@ 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 } // 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 } // 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 } // 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -99,7 +102,7 @@ func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { 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() @@ -118,7 +121,7 @@ func (e *Executor) describeFragmentFrom(s *ast.DescribeFragmentFromStmt) error { 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 diff --git a/mdl/executor/cmd_imagecollections.go b/mdl/executor/cmd_imagecollections.go index 126ad7c6..97537064 100644 --- a/mdl/executor/cmd_imagecollections.go +++ b/mdl/executor/cmd_imagecollections.go @@ -15,19 +15,20 @@ 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() } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } // 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) } @@ -67,19 +68,20 @@ func (e *Executor) execCreateImageCollection(s *ast.CreateImageCollectionStmt) e } // Invalidate hierarchy cache so the new collection's container is visible - e.invalidateHierarchy() + invalidateHierarchy(ctx) - fmt.Fprintf(e.output, "Created image collection: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created image collection: %s\n", s.Name) return nil } // 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() } - 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()) } @@ -88,18 +90,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 } // describeImageCollection handles DESCRIBE IMAGE COLLECTION Module.Name. -func (e *Executor) describeImageCollection(name ast.QualifiedName) error { - ic := e.findImageCollection(name.Module, name.Name) +func describeImageCollection(ctx *ExecContext, name ast.QualifiedName) error { + ic := findImageCollection(ctx, name.Module, name.Name) if ic == nil { return mdlerrors.NewNotFound("image collection", name.String()) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -107,7 +109,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 +120,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 +135,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,11 +154,11 @@ 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 } @@ -197,13 +199,14 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -228,17 +231,18 @@ func (e *Executor) showImageCollections(moduleName string) error { } result.Summary = fmt.Sprintf("(%d image collection(s))", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // findImageCollection finds an image collection by module and name. -func (e *Executor) findImageCollection(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 } diff --git a/mdl/executor/cmd_import.go b/mdl/executor/cmd_import.go index 30d4cbf4..c7b71e7e 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(ctx.Context, 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,10 +87,10 @@ 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) } } @@ -98,12 +99,13 @@ func (e *Executor) execImport(s *ast.ImportStmt) error { // 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) @@ -118,7 +120,7 @@ func (e *Executor) resolveImportLinks(ctx context.Context, mendixConn *sqllib.Co 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) } @@ -136,18 +138,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 +157,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 +242,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 +285,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 +317,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 +349,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_import_mappings.go b/mdl/executor/cmd_import_mappings.go index c8cb861c..d59afdea 100644 --- a/mdl/executor/cmd_import_mappings.go +++ b/mdl/executor/cmd_import_mappings.go @@ -4,6 +4,7 @@ package executor import ( "fmt" + "io" "sort" "strings" @@ -14,7 +15,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() } @@ -24,7 +26,7 @@ func (e *Executor) showImportMappings(inModule string) error { return mdlerrors.NewBackend("list import mappings", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -57,9 +59,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 } @@ -73,11 +75,12 @@ func (e *Executor) showImportMappings(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) } // 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,31 +94,31 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return err } 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 } @@ -132,7 +135,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 +145,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 +154,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,17 +186,18 @@ 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() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return mdlerrors.NewNotFound("module", s.Name.Module) } @@ -231,8 +235,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 +377,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 +395,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..854e04dd 100644 --- a/mdl/executor/cmd_javaactions.go +++ b/mdl/executor/cmd_javaactions.go @@ -18,9 +18,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -62,11 +63,12 @@ func (e *Executor) showJavaActions(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) } // 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 +162,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 +172,27 @@ 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 } // 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,13 +248,14 @@ 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() } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -270,7 +273,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,13 +282,14 @@ 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() } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -424,7 +428,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..4898391c 100644 --- a/mdl/executor/cmd_javascript_actions.go +++ b/mdl/executor/cmd_javascript_actions.go @@ -16,8 +16,9 @@ import ( ) // showJavaScriptActions handles SHOW JAVASCRIPT ACTIONS command. -func (e *Executor) showJavaScriptActions(moduleName string) error { - h, err := e.getHierarchy() +func showJavaScriptActions(ctx *ExecContext, moduleName string) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -61,11 +62,12 @@ func (e *Executor) showJavaScriptActions(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) } // 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 +180,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,24 +189,24 @@ 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) } } @@ -212,12 +214,12 @@ func (e *Executor) describeJavaScriptAction(name ast.QualifiedName) error { } // 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..21ea7daa 100644 --- a/mdl/executor/cmd_jsonstructures.go +++ b/mdl/executor/cmd_jsonstructures.go @@ -15,13 +15,14 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -65,18 +66,18 @@ func (e *Executor) showJsonStructures(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) } // 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 { + 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 } @@ -87,24 +88,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,18 +119,18 @@ 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 } @@ -172,13 +173,14 @@ 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() } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -186,7 +188,7 @@ func (e *Executor) execCreateJsonStructure(s *ast.CreateJsonStructureStmt) 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) } @@ -194,7 +196,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 @@ -230,23 +232,24 @@ func (e *Executor) execCreateJsonStructure(s *ast.CreateJsonStructureStmt) error } // Invalidate hierarchy cache - e.invalidateHierarchy() + invalidateHierarchy(ctx) action := "Created" 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,18 +258,19 @@ 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 } - 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 b1144879..86b67393 100644 --- a/mdl/executor/cmd_languages.go +++ b/mdl/executor/cmd_languages.go @@ -10,12 +10,12 @@ 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 { + 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 +27,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 } @@ -46,5 +46,7 @@ func (e *Executor) showLanguages() 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 bf02e62d..af2b8aa5 100644 --- a/mdl/executor/cmd_layouts.go +++ b/mdl/executor/cmd_layouts.go @@ -12,9 +12,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -59,5 +60,5 @@ func (e *Executor) showLayouts(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) } diff --git a/mdl/executor/cmd_lint.go b/mdl/executor/cmd_lint.go index eb36bd29..137f2585 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 err := e.buildCatalog(false); err != nil { + if ctx.Catalog == nil { + fmt.Fprintln(ctx.Output, "Building catalog for linting...") + if err := buildCatalog(ctx, 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,18 @@ 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 --- diff --git a/mdl/executor/cmd_mermaid.go b/mdl/executor/cmd_mermaid.go index 6f746ea2..3febd039 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,19 +33,25 @@ 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 { - module, err := e.findModule(moduleName) +func domainModelToMermaid(ctx *ExecContext, moduleName string) error { + e := ctx.executor + module, err := findModule(ctx, moduleName) if err != nil { return err } @@ -61,7 +69,7 @@ func (e *Executor) domainModelToMermaid(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 { @@ -176,13 +184,14 @@ 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 { - h, err := e.getHierarchy() +func microflowToMermaid(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + h, err := getHierarchy(ctx) 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,13 +363,14 @@ 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 { - h, err := e.getHierarchy() +func pageToMermaid(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -385,7 +395,7 @@ func (e *Executor) pageToMermaid(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") @@ -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_microflow_elk.go b/mdl/executor/cmd_microflow_elk.go index d07a4202..f8a7ab32 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() } @@ -72,7 +74,7 @@ func (e *Executor) MicroflowELK(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) } @@ -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..369321f6 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,13 +33,14 @@ 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() } // Find or auto-create module - module, err := e.findOrCreateModule(s.Name.Module) + module, err := findOrCreateModule(ctx, s.Name.Module) if err != nil { return err } @@ -46,7 +48,7 @@ func (e *Executor) execCreateMicroflow(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) } @@ -61,7 +63,7 @@ func (e *Executor) execCreateMicroflow(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)") } @@ -135,7 +137,7 @@ func (e *Executor) execCreateMicroflow(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)) } @@ -165,7 +167,7 @@ func (e *Executor) execCreateMicroflow(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)) } @@ -200,9 +202,9 @@ func (e *Executor) execCreateMicroflow(s *ast.CreateMicroflowStmt) error { } } // Get hierarchy for resolving page/microflow references - hierarchy, _ := e.getHierarchy() + hierarchy, _ := getHierarchy(ctx) - 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 @@ -248,6 +250,6 @@ func (e *Executor) execCreateMicroflow(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 0f9a59c6..8e8368fd 100644 --- a/mdl/executor/cmd_microflows_drop.go +++ b/mdl/executor/cmd_microflows_drop.go @@ -11,13 +11,14 @@ 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() } // Get hierarchy for module/folder resolution - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -40,8 +41,8 @@ func (e *Executor) execDropMicroflow(s *ast.DropMicroflowStmt) error { if e.cache != nil && e.cache.createdMicroflows != nil { delete(e.cache.createdMicroflows, qualifiedName) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped microflow: %s.%s\n", s.Name.Module, s.Name.Name) + 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 ce277326..d2667885 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 @@ -499,7 +503,7 @@ func (e *Executor) 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) } @@ -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..f2bd58d7 100644 --- a/mdl/executor/cmd_microflows_show.go +++ b/mdl/executor/cmd_microflows_show.go @@ -15,9 +15,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -75,13 +76,14 @@ func (e *Executor) showMicroflows(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 (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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -139,7 +141,7 @@ func (e *Executor) showNanoflows(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. @@ -177,9 +179,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -239,7 +242,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 +257,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 +278,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,14 +302,15 @@ 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 { - h, err := e.getHierarchy() +func describeNanoflow(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -370,7 +374,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 +389,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 +408,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,14 +419,15 @@ 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) { - h, err := e.getHierarchy() +func describeMicroflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return "", nil, mdlerrors.NewBackend("build hierarchy", err) } @@ -460,7 +465,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 +476,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 +504,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 +518,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 +534,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 +563,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 +616,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 +659,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 +667,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 +675,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 +715,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 +745,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 +756,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 +768,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 +785,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 +810,5 @@ func (e *Executor) collectReachableNodes( traverse(startID) return result } + +// --- Executor method wrappers for callers in unmigrated code --- 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/cmd_misc.go b/mdl/executor/cmd_misc.go index be003093..f55fb6d4 100644 --- a/mdl/executor/cmd_misc.go +++ b/mdl/executor/cmd_misc.go @@ -20,31 +20,33 @@ 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}) } // execRefresh handles REFRESH statements (alias for UPDATE). -func (e *Executor) execRefresh() error { - return e.execUpdate() +func execRefresh(ctx *ExecContext) error { + return execUpdate(ctx) } // 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 } // execHelp handles HELP statements. -func (e *Executor) execHelp() error { +func execHelp(ctx *ExecContext) error { help := `MDL Commands: Connection: @@ -317,22 +319,23 @@ Other: Statement Terminator: Use ; or / to end statements ` - fmt.Fprint(e.output, help) + fmt.Fprint(ctx.Output, help) return nil } // 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 } @@ -342,12 +345,13 @@ func (e *Executor) showVersion() error { // 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 } // 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,24 +376,24 @@ 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 } diff --git a/mdl/executor/cmd_module_overview.go b/mdl/executor/cmd_module_overview.go index 31f5e1c0..4ca2c2cf 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,18 +47,19 @@ 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() } // 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) } // 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_modules.go b/mdl/executor/cmd_modules.go index b43b5b1e..ffcedb4b 100644 --- a/mdl/executor/cmd_modules.go +++ b/mdl/executor/cmd_modules.go @@ -15,7 +15,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 +29,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 } } @@ -43,9 +44,9 @@ func (e *Executor) execCreateModule(s *ast.CreateModuleStmt) error { } // Invalidate cache so new module is visible - e.invalidateModuleCache() + invalidateModuleCache(ctx) - fmt.Fprintf(e.output, "Created module: %s\n", s.Name) + fmt.Fprintf(ctx.Output, "Created module: %s\n", s.Name) return nil } @@ -58,7 +59,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() } @@ -82,7 +84,7 @@ func (e *Executor) execDropModule(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 @@ -92,7 +94,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 +109,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 +117,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 +131,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 +144,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 +157,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 +170,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 +183,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 +196,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 +209,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 +222,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 +235,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 +248,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 +261,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 +275,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,16 +329,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 + // 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 @@ -374,20 +379,21 @@ func (e *Executor) getModuleContainers(moduleID model.ID) map[model.ID]bool { } // 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) } @@ -563,11 +569,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() } @@ -591,25 +598,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) } } } @@ -619,8 +626,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) } } } @@ -630,19 +637,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) } } } @@ -651,8 +658,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) } } } @@ -662,8 +669,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) } } } @@ -673,8 +680,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) } } } @@ -684,8 +691,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) } } } @@ -695,8 +702,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) } } } @@ -706,8 +713,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) } } } @@ -717,15 +724,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 { @@ -735,8 +742,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) } } } @@ -750,8 +757,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) } } } @@ -764,8 +771,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) } } } @@ -773,8 +780,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) } } } @@ -782,8 +789,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) } } } @@ -791,20 +798,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 } @@ -896,3 +903,5 @@ func (e *Executor) sortEntitiesByGeneralization(entities []*domainmodel.Entity, return sorted } + +// Executor method wrappers for callers in unmigrated files. diff --git a/mdl/executor/cmd_move.go b/mdl/executor/cmd_move.go index b531c2fe..76ecdcb3 100644 --- a/mdl/executor/cmd_move.go +++ b/mdl/executor/cmd_move.go @@ -13,13 +13,14 @@ 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() } // 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) } @@ -28,7 +29,7 @@ func (e *Executor) execMove(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) } @@ -39,13 +40,13 @@ 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) 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) } @@ -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,20 +106,21 @@ 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 { return mdlerrors.NewBackend("list pages", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -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,14 +143,15 @@ 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 { return mdlerrors.NewBackend("list microflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -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,14 +174,15 @@ 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 { return mdlerrors.NewBackend("list snippets", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -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,14 +205,15 @@ 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 { return mdlerrors.NewBackend("list nanoflows", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -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,8 +302,9 @@ 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 { - enum := e.findEnumeration(name.Module, name.Name) +func moveEnumeration(ctx *ExecContext, name ast.QualifiedName, targetContainerID model.ID, targetModuleName string) error { + e := ctx.executor + enum := findEnumeration(ctx, name.Module, name.Name) if enum == nil { return mdlerrors.NewNotFound("enumeration", name.String()) } @@ -311,24 +319,25 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", 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,13 +359,14 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", 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_navigation.go b/mdl/executor/cmd_navigation.go index 590463ab..5fedf117 100644 --- a/mdl/executor/cmd_navigation.go +++ b/mdl/executor/cmd_navigation.go @@ -14,7 +14,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,7 +69,7 @@ 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 } @@ -100,14 +101,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 } @@ -153,12 +155,13 @@ func (e *Executor) showNavigation() 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) } // 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,13 +172,13 @@ 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 @@ -183,29 +186,30 @@ func (e *Executor) showNavigationMenu(profileName *ast.QualifiedName) error { // 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,11 +217,11 @@ 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 @@ -225,7 +229,8 @@ func (e *Executor) showNavigationHomes() error { // 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 +239,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 +247,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 } } @@ -251,64 +256,64 @@ func (e *Executor) describeNavigation(name ast.QualifiedName) error { } // 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_odata.go b/mdl/executor/cmd_odata.go index 95b84626..2e945685 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -35,13 +35,15 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -78,7 +80,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 } @@ -94,17 +96,19 @@ func (e *Executor) showODataClients(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -114,7 +118,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 +126,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,37 +195,39 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -255,7 +261,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 } @@ -271,17 +277,19 @@ func (e *Executor) showODataServices(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -291,7 +299,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 +307,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 +340,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 +379,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 +389,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 +409,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,36 +436,38 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -495,7 +505,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 } @@ -511,13 +521,15 @@ func (e *Executor) showExternalEntities(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. // 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) @@ -527,7 +539,7 @@ func (e *Executor) showExternalActions(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) } @@ -610,7 +622,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 } @@ -644,17 +656,19 @@ func (e *Executor) showExternalActions(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -675,7 +689,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 +697,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 +723,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 +739,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 +754,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() } @@ -750,7 +766,7 @@ func (e *Executor) execCreateExternalEntity(s *ast.CreateExternalEntityStmt) err } // Find module - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -807,7 +823,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 +850,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 +859,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() } @@ -852,7 +870,7 @@ func (e *Executor) createODataClient(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 } @@ -860,7 +878,7 @@ func (e *Executor) createODataClient(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) @@ -936,8 +954,8 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { if err := e.writer.UpdateConsumedODataService(svc); err != nil { return mdlerrors.NewBackend("update OData client", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified OData client: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + 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)) @@ -948,7 +966,7 @@ func (e *Executor) createODataClient(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) } @@ -1005,7 +1023,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 @@ -1016,8 +1034,8 @@ func (e *Executor) createODataClient(stmt *ast.CreateODataClientStmt) error { if err := e.writer.CreateConsumedODataService(newSvc); err != nil { 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) + 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 if doc, err := mpr.ParseEdmx(newSvc.Metadata); err == nil { @@ -1027,14 +1045,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() } @@ -1044,7 +1064,7 @@ func (e *Executor) alterODataClient(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) } @@ -1113,8 +1133,8 @@ func (e *Executor) alterODataClient(stmt *ast.AlterODataClientStmt) error { if err := e.writer.UpdateConsumedODataService(svc); err != nil { return mdlerrors.NewBackend("alter OData client", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered OData client: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Altered OData client: %s.%s\n", modName, svc.Name) return nil } } @@ -1123,7 +1143,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() } @@ -1133,7 +1155,7 @@ func (e *Executor) dropODataClient(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) } @@ -1145,8 +1167,8 @@ func (e *Executor) dropODataClient(stmt *ast.DropODataClientStmt) error { if err := e.writer.DeleteConsumedODataService(svc.ID); err != nil { return mdlerrors.NewBackend("drop OData client", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped OData client: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Dropped OData client: %s.%s\n", modName, svc.Name) return nil } } @@ -1155,7 +1177,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() } @@ -1164,7 +1188,7 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { 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 } @@ -1172,7 +1196,7 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { // 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) @@ -1207,8 +1231,8 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { if err := e.writer.UpdatePublishedODataService(svc); err != nil { return mdlerrors.NewBackend("update OData service", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Modified OData service: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + 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)) @@ -1219,7 +1243,7 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) 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) } @@ -1251,13 +1275,15 @@ func (e *Executor) createODataService(stmt *ast.CreateODataServiceStmt) error { if err := e.writer.CreatePublishedODataService(newSvc); err != nil { 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) + invalidateHierarchy(ctx) + 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() } @@ -1267,7 +1293,7 @@ func (e *Executor) alterODataService(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) } @@ -1302,8 +1328,8 @@ func (e *Executor) alterODataService(stmt *ast.AlterODataServiceStmt) error { if err := e.writer.UpdatePublishedODataService(svc); err != nil { return mdlerrors.NewBackend("alter OData service", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Altered OData service: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Altered OData service: %s.%s\n", modName, svc.Name) return nil } } @@ -1312,7 +1338,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() } @@ -1322,7 +1350,7 @@ func (e *Executor) dropODataService(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) } @@ -1334,8 +1362,8 @@ func (e *Executor) dropODataService(stmt *ast.DropODataServiceStmt) error { if err := e.writer.DeletePublishedODataService(svc.ID); err != nil { return mdlerrors.NewBackend("drop OData service", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped OData service: %s.%s\n", modName, svc.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Dropped OData service: %s.%s\n", modName, svc.Name) return nil } } @@ -1433,3 +1461,5 @@ func fetchODataMetadata(metadataUrl string) (metadata string, hash string, err e hash = fmt.Sprintf("%x", h) return metadata, hash, nil } + +// Executor wrappers for unmigrated callers. diff --git a/mdl/executor/cmd_oql_plan.go b/mdl/executor/cmd_oql_plan.go index 72958f23..3a6f18cf 100644 --- a/mdl/executor/cmd_oql_plan.go +++ b/mdl/executor/cmd_oql_plan.go @@ -60,14 +60,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 +474,5 @@ func buildScalarSubqueryTable(sel ast.OQLSelectItem, selectIndex, tableOffset in return t } + +// --- Executor method wrapper for backward compatibility --- diff --git a/mdl/executor/cmd_page_wireframe.go b/mdl/executor/cmd_page_wireframe.go index 48305885..31204839 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() } @@ -100,7 +102,7 @@ func (e *Executor) PageWireframeJSON(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) } @@ -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,17 +198,18 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -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..a88f869e 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,8 +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 { - h, err := e.getHierarchy() +func getModuleID(ctx *ExecContext, containerID model.ID) model.ID { + h, err := getHierarchy(ctx) if err != nil { return containerID } @@ -232,8 +233,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 { - h, err := e.getHierarchy() +func getModuleName(ctx *ExecContext, moduleID model.ID) string { + h, err := getHierarchy(ctx) if err != nil { return "" } @@ -334,7 +335,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 +347,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 +362,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 +374,20 @@ 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) 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..d515ec2e 100644 --- a/mdl/executor/cmd_pages_create_v3.go +++ b/mdl/executor/cmd_pages_create_v3.go @@ -15,14 +15,15 @@ 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() } // 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 @@ -30,7 +31,7 @@ func (e *Executor) execCreatePageV3(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) } @@ -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()) @@ -92,20 +93,21 @@ func (e *Executor) execCreatePageV3(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(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() } // 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) } @@ -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()) @@ -160,8 +162,8 @@ func (e *Executor) execCreateSnippetV3(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(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..ed0fd762 100644 --- a/mdl/executor/cmd_pages_describe.go +++ b/mdl/executor/cmd_pages_describe.go @@ -20,9 +20,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -54,11 +55,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 +80,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 +89,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 +136,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 +153,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,9 +176,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -209,11 +211,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 +226,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,27 +242,28 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -292,11 +295,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 +308,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 +343,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 +382,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,13 +437,14 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return string(layoutID) } @@ -610,7 +616,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 +645,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 diff --git a/mdl/executor/cmd_pages_describe_output.go b/mdl/executor/cmd_pages_describe_output.go index 9345f8e0..e731dae9 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 "" } @@ -974,7 +976,7 @@ func (e *Executor) getPageQualifiedName(pageID model.ID) string { if err != nil { return "" } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { 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..cb56a1f1 100644 --- a/mdl/executor/cmd_pages_show.go +++ b/mdl/executor/cmd_pages_show.go @@ -11,9 +11,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -72,5 +73,5 @@ func (e *Executor) showPages(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) } diff --git a/mdl/executor/cmd_published_rest.go b/mdl/executor/cmd_published_rest.go index eb52985e..4e94df21 100644 --- a/mdl/executor/cmd_published_rest.go +++ b/mdl/executor/cmd_published_rest.go @@ -14,13 +14,15 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -57,7 +59,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 } @@ -72,17 +74,19 @@ func (e *Executor) showPublishedRestServices(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -97,24 +101,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 +136,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,12 +160,14 @@ 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return nil, err } @@ -176,12 +182,14 @@ 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() } - 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 @@ -189,7 +197,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) @@ -201,14 +209,14 @@ func (e *Executor) execCreatePublishedRestService(s *ast.CreatePublishedRestServ } } - 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) } @@ -245,13 +253,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() } @@ -261,7 +271,7 @@ func (e *Executor) execDropPublishedRestService(s *ast.DropPublishedRestServiceS return mdlerrors.NewBackend("list published REST services", err) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -274,7 +284,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,18 +310,20 @@ 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() } - 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 } - 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 +376,9 @@ 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. diff --git a/mdl/executor/cmd_rename.go b/mdl/executor/cmd_rename.go index 66b05062..959aeaf6 100644 --- a/mdl/executor/cmd_rename.go +++ b/mdl/executor/cmd_rename.go @@ -13,37 +13,39 @@ 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) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -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 } @@ -89,22 +91,23 @@ func (e *Executor) execRenameEntity(s *ast.RenameStmt) error { return mdlerrors.NewBackend("update entity name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) - 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 - module, err := e.findModule(oldModuleName) + module, err := findModule(ctx, oldModuleName) if err != nil { return err } @@ -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 } @@ -136,12 +139,12 @@ func (e *Executor) execRenameModule(s *ast.RenameStmt) error { return mdlerrors.NewBackend("update module name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) - 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,12 +153,13 @@ 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 // Verify the document exists - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return err } @@ -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 } @@ -224,17 +228,18 @@ func (e *Executor) execRenameDocument(s *ast.RenameStmt, docType string) error { return mdlerrors.NewBackend(fmt.Sprintf("rename %s", docType), err) } - e.invalidateHierarchy() + invalidateHierarchy(ctx) - 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 @@ -243,7 +248,7 @@ func (e *Executor) execRenameEnumeration(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 } @@ -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 } @@ -277,22 +282,23 @@ func (e *Executor) execRenameEnumeration(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(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 - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -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 } @@ -334,20 +340,20 @@ func (e *Executor) execRenameAssociation(s *ast.RenameStmt) error { return mdlerrors.NewBackend("update association name", err) } - e.invalidateHierarchy() - e.invalidateDomainModelsCache() + invalidateHierarchy(ctx) + invalidateDomainModelsCache(ctx) - 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_rest_clients.go b/mdl/executor/cmd_rest_clients.go index c4b304a5..22e62cf9 100644 --- a/mdl/executor/cmd_rest_clients.go +++ b/mdl/executor/cmd_rest_clients.go @@ -22,13 +22,15 @@ 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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -64,7 +66,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 } @@ -79,17 +81,19 @@ func (e *Executor) showRestClients(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. -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) } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -98,7 +102,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 +110,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,27 +280,29 @@ 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() } // 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 } 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) } @@ -319,7 +325,7 @@ func (e *Executor) createRestClient(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) } @@ -354,7 +360,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 +461,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() } @@ -465,7 +473,7 @@ func (e *Executor) dropRestClient(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) } @@ -477,7 +485,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 +500,5 @@ func formatRestAuthValue(value string) string { } return "'" + value + "'" } + +// Executor wrappers for unmigrated callers. diff --git a/mdl/executor/cmd_search.go b/mdl/executor/cmd_search.go index ccef9093..7b5f2b3f 100644 --- a/mdl/executor/cmd_search.go +++ b/mdl/executor/cmd_search.go @@ -11,22 +11,22 @@ 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 { 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 } 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,38 +58,38 @@ 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) - e.outputCatalogResults(result) + fmt.Fprintf(ctx.Output, "Found %d caller(s)\n", result.Count) + outputCatalogResults(ctx, 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 { 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 } 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,34 +121,34 @@ 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) - e.outputCatalogResults(result) + fmt.Fprintf(ctx.Output, "Found %d callee(s)\n", result.Count) + outputCatalogResults(ctx, 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 { 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 } 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,35 +158,35 @@ 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) - e.outputCatalogResults(result) + fmt.Fprintf(ctx.Output, "Found %d reference(s)\n", result.Count) + 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 (e *Executor) execShowImpact(s *ast.ShowStmt) error { +func execShowImpact(ctx *ExecContext, s *ast.ShowStmt) error { 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 } 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 +196,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 +216,16 @@ 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) - e.outputCatalogResults(result) + fmt.Fprintf(ctx.Output, "Found %d affected element(s)\n", result.Count) + outputCatalogResults(ctx, result) return nil } + +// --- Executor method wrappers for backward compatibility --- diff --git a/mdl/executor/cmd_security.go b/mdl/executor/cmd_security.go index e7977594..96f7d4a9 100644 --- a/mdl/executor/cmd_security.go +++ b/mdl/executor/cmd_security.go @@ -14,41 +14,43 @@ 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 { - h, err := e.getHierarchy() +func showModuleRoles(ctx *ExecContext, moduleName string) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -77,11 +79,12 @@ func (e *Executor) showModuleRoles(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. -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) @@ -104,19 +107,20 @@ func (e *Executor) showUserRoles() error { } result.Summary = fmt.Sprintf("(%d user roles)", len(result.Rows)) - return e.writeResult(result) + return writeResult(ctx, result) } // 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 } @@ -130,16 +134,17 @@ func (e *Executor) showDemoUsers() 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. -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") } - module, err := e.findModule(name.Module) + module, err := findModule(ctx, name.Module) if err != nil { return err } @@ -161,7 +166,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 +176,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 +189,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 +216,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,26 +236,27 @@ 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") } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -264,12 +270,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,12 +285,13 @@ 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") } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -298,12 +305,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,17 +320,18 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -355,9 +363,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 +382,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 +406,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 +444,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 +478,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,26 +509,27 @@ 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 { - h, err := e.getHierarchy() +func showSecurityMatrixJSON(ctx *ExecContext, moduleName string) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -630,12 +639,13 @@ func (e *Executor) showSecurityMatrixJSON(moduleName string) error { }) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // describeModuleRole handles DESCRIBE MODULE ROLE Module.RoleName. -func (e *Executor) describeModuleRole(name ast.QualifiedName) error { - h, err := e.getHierarchy() +func describeModuleRole(ctx *ExecContext, name ast.QualifiedName) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -652,12 +662,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 +682,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 +695,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 +704,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 +721,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 +730,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 +760,6 @@ 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). diff --git a/mdl/executor/cmd_security_write.go b/mdl/executor/cmd_security_write.go index 23f81b6d..10fb0755 100644 --- a/mdl/executor/cmd_security_write.go +++ b/mdl/executor/cmd_security_write.go @@ -15,12 +15,13 @@ 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() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -41,19 +42,20 @@ 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() } - module, err := e.findModule(s.Name.Module) + module, err := findModule(ctx, s.Name.Module) if err != nil { return err } @@ -83,12 +85,12 @@ 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) } } // 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 { @@ -98,7 +100,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 +113,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 +126,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 +139,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 +148,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 +157,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 +190,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 +199,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 +243,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,17 +275,18 @@ 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() } - module, err := e.findModule(s.Entity.Module) + module, err := findModule(ctx, s.Entity.Module) if err != nil { return err } @@ -419,24 +425,25 @@ 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() } - module, err := e.findModule(s.Entity.Module) + module, err := findModule(ctx, s.Entity.Module) if err != nil { return err } @@ -490,11 +497,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 +512,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,12 +525,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -543,7 +551,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 +577,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,12 +588,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -625,9 +634,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,12 +645,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -661,7 +671,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 +697,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,12 +708,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -743,9 +754,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,20 +767,21 @@ 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 { - module, err := e.findModule(role.Module) +func validateModuleRole(ctx *ExecContext, role ast.QualifiedName) error { + e := ctx.executor + 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) } @@ -789,7 +801,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 +829,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 +840,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 +890,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 +898,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 +909,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 +959,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 +986,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,12 +995,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1004,7 +1021,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 +1047,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,12 +1058,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1086,9 +1104,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,18 +1119,19 @@ 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() } - 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 } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1131,7 +1150,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 +1176,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,12 +1187,13 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -1212,9 +1232,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,12 +1243,13 @@ 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() } - modules, err := e.getModulesFromCache() + modules, err := getModulesFromCache(ctx) if err != nil { return err } @@ -1249,14 +1270,17 @@ 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). diff --git a/mdl/executor/cmd_settings.go b/mdl/executor/cmd_settings.go index edcbe66a..4a2d2b1d 100644 --- a/mdl/executor/cmd_settings.go +++ b/mdl/executor/cmd_settings.go @@ -13,7 +13,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() } @@ -79,11 +80,12 @@ func (e *Executor) showSettings() error { tr.Rows = append(tr.Rows, []any{"Web UI Settings", "OptimizedClient: " + ps.WebUI.UseOptimizedClient}) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // 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 +116,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 +133,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 +145,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,7 +162,7 @@ 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")) } } @@ -168,7 +170,8 @@ func (e *Executor) describeSettings() error { } // 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 +252,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 +266,12 @@ 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 { +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 +320,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 +360,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 +390,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 +461,13 @@ 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 } // 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 +490,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 } } diff --git a/mdl/executor/cmd_snippets.go b/mdl/executor/cmd_snippets.go index 3eb51989..e16148c7 100644 --- a/mdl/executor/cmd_snippets.go +++ b/mdl/executor/cmd_snippets.go @@ -12,9 +12,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -58,5 +59,5 @@ func (e *Executor) showSnippets(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) } diff --git a/mdl/executor/cmd_sql.go b/mdl/executor/cmd_sql.go index 4e0b7de3..42a0e4d4 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,23 @@ 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. diff --git a/mdl/executor/cmd_structure.go b/mdl/executor/cmd_structure.go index eebfb157..8d2466f7 100644 --- a/mdl/executor/cmd_structure.go +++ b/mdl/executor/cmd_structure.go @@ -17,7 +17,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() } @@ -25,58 +26,58 @@ func (e *Executor) execShowStructure(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) } // 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) } } // 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 { + 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{ @@ -103,7 +104,7 @@ func (e *Executor) structureDepth1JSON(modules []structureModule) error { beServiceCounts[m.Name], }) } - return e.writeResult(tr) + return writeResult(ctx, tr) } // structureModule holds module info for structure output. @@ -113,8 +114,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 +182,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 +252,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,9 +274,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return counts } @@ -313,9 +315,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -394,12 +397,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 +412,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 +420,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 +428,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 +456,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,9 +477,10 @@ 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() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -548,12 +552,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 +567,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 +575,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 +583,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 +607,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 +615,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 +635,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 +671,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 +700,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 +718,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 +748,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 +766,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 +785,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 +832,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 +845,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 +867,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 +902,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 +944,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..48ecd04e 100644 --- a/mdl/executor/cmd_styling.go +++ b/mdl/executor/cmd_styling.go @@ -19,7 +19,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 +33,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 +42,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,11 +60,11 @@ 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) } } @@ -70,39 +72,39 @@ func (e *Executor) execShowDesignProperties(s *ast.ShowDesignPropertiesStmt) err } // 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,12 +112,14 @@ 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -141,7 +145,7 @@ func (e *Executor) execDescribeStyling(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() @@ -161,11 +165,11 @@ func (e *Executor) execDescribeStyling(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 { - 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,36 +180,36 @@ 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, "]") } } @@ -252,7 +256,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() } @@ -260,21 +266,23 @@ func (e *Executor) execAlterStyling(s *ast.AlterStylingStmt) error { return mdlerrors.NewNotConnectedWrite() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } 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 { +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 +325,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 +374,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 +519,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) @@ -525,7 +537,9 @@ func (e *Executor) findPageByName(name ast.QualifiedName, h *ContainerHierarchy) } // 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) diff --git a/mdl/executor/cmd_widgets.go b/mdl/executor/cmd_widgets.go index 6fa4212b..0710af9c 100644 --- a/mdl/executor/cmd_widgets.go +++ b/mdl/executor/cmd_widgets.go @@ -15,13 +15,15 @@ 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() } // 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) } @@ -48,21 +50,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 +72,17 @@ 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 } // 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() } @@ -87,18 +91,18 @@ func (e *Executor) execUpdateWidgets(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) } // 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,30 +110,30 @@ 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 @@ -146,7 +150,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 +171,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 +201,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 +211,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 +237,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 +269,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 +286,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 +334,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 +345,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/cmd_workflows.go b/mdl/executor/cmd_workflows.go index fdd8d6ae..fc0c391b 100644 --- a/mdl/executor/cmd_workflows.go +++ b/mdl/executor/cmd_workflows.go @@ -14,8 +14,9 @@ import ( ) // showWorkflows handles SHOW WORKFLOWS command. -func (e *Executor) showWorkflows(moduleName string) error { - h, err := e.getHierarchy() +func showWorkflows(ctx *ExecContext, moduleName string) error { + e := ctx.executor + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -66,7 +67,7 @@ func (e *Executor) showWorkflows(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) } // countWorkflowActivities counts total activities, user tasks, and decisions in a workflow. @@ -131,18 +132,19 @@ 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 } // 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) { - h, err := e.getHierarchy() +func describeWorkflowToString(ctx *ExecContext, name ast.QualifiedName) (string, map[string]elkSourceRange, error) { + e := ctx.executor + 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 0694ee5e..c0015e04 100644 --- a/mdl/executor/cmd_workflows_write.go +++ b/mdl/executor/cmd_workflows_write.go @@ -16,18 +16,19 @@ 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() } - 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) } @@ -98,7 +99,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) @@ -122,18 +123,19 @@ func (e *Executor) execCreateWorkflow(s *ast.CreateWorkflowStmt) error { return mdlerrors.NewBackend("create workflow", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Created workflow: %s.%s\n", s.Name.Module, s.Name.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Created workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } // 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() } - h, err := e.getHierarchy() + h, err := getHierarchy(ctx) if err != nil { return mdlerrors.NewBackend("build hierarchy", err) } @@ -150,8 +152,8 @@ func (e *Executor) execDropWorkflow(s *ast.DropWorkflowStmt) error { if err := e.writer.DeleteWorkflow(wf.ID); err != nil { return mdlerrors.NewBackend("delete workflow", err) } - e.invalidateHierarchy() - fmt.Fprintf(e.output, "Dropped workflow: %s.%s\n", s.Name.Module, s.Name.Name) + invalidateHierarchy(ctx) + fmt.Fprintf(ctx.Output, "Dropped workflow: %s.%s\n", s.Name.Module, s.Name.Name) return nil } } @@ -602,36 +604,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 +641,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 +650,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 +669,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 +722,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/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_connect.go b/mdl/executor/executor_connect.go index a0d482bf..4c3a178d 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,26 @@ 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 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/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/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..98269be7 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,23 @@ 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) 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..e1465111 100644 --- a/mdl/executor/helpers.go +++ b/mdl/executor/helpers.go @@ -20,36 +20,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 +67,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 } @@ -75,14 +77,14 @@ func (e *Executor) findOrCreateModule(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 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 +100,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 +134,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 +153,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 +176,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 +190,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 +216,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 +234,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 +243,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 +252,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 +261,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 +270,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 +364,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 +383,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 +402,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 +421,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 +440,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 +468,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 +486,10 @@ func (e *Executor) buildJavaActionQualifiedNames() map[string]bool { return result } +// ---------------------------------------------------------------------------- +// Executor method wrappers (for callers in unmigrated files) +// ---------------------------------------------------------------------------- + // ---------------------------------------------------------------------------- // Data Type Conversion // ---------------------------------------------------------------------------- diff --git a/mdl/executor/hierarchy.go b/mdl/executor/hierarchy.go index c8101f02..cde6bee3 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,47 @@ 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())) +} diff --git a/mdl/executor/oql_type_inference.go b/mdl/executor/oql_type_inference.go index c314262c..d0279e98 100644 --- a/mdl/executor/oql_type_inference.go +++ b/mdl/executor/oql_type_inference.go @@ -24,8 +24,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 +64,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) } @@ -279,8 +279,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 +295,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) @@ -481,18 +481,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 +504,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 +531,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 +540,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 +555,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 +571,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 +592,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 +614,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 +641,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} } @@ -657,14 +657,15 @@ func (e *Executor) inferAttributeType(attrPath string, col *OQLColumnInfo) ast.D } // findEntity looks up an entity by module and name. -func (e *Executor) findEntity(moduleName, entityName string) (*domainmodel.Entity, error) { +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 44cb28e2..d1b250d7 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 execConnect(ctx, 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 execDisconnect(ctx) }) - 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 execStatus(ctx) }) } 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 execCreateModule(ctx, 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 execDropModule(ctx, 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 execCreateEnumeration(ctx, 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 execAlterEnumeration(ctx, 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 execDropEnumeration(ctx, 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 createConstant(ctx, 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 dropConstant(ctx, 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 createDatabaseConnection(ctx, 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 execCreateEntity(ctx, 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 execCreateViewEntity(ctx, 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 execAlterEntity(ctx, 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 execDropEntity(ctx, 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 execCreateAssociation(ctx, 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 execAlterAssociation(ctx, 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 execDropAssociation(ctx, 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 execCreateMicroflow(ctx, 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 execDropMicroflow(ctx, 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 execCreatePageV3(ctx, 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 execDropPage(ctx, 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 execCreateSnippetV3(ctx, 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 execDropSnippet(ctx, 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 execDropJavaAction(ctx, 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 execCreateJavaAction(ctx, 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 execDropFolder(ctx, 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 execMoveFolder(ctx, 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 execMove(ctx, 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 execRename(ctx, 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 execCreateModuleRole(ctx, 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 execDropModuleRole(ctx, 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 execCreateUserRole(ctx, 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 execAlterUserRole(ctx, 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 execDropUserRole(ctx, 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 execGrantEntityAccess(ctx, 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 execRevokeEntityAccess(ctx, 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 execGrantMicroflowAccess(ctx, 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 execRevokeMicroflowAccess(ctx, 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 execGrantPageAccess(ctx, 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 execRevokePageAccess(ctx, 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 execGrantWorkflowAccess(ctx, 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 execRevokeWorkflowAccess(ctx, 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 execAlterProjectSecurity(ctx, 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 execCreateDemoUser(ctx, 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 execDropDemoUser(ctx, 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 execUpdateSecurity(ctx, 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 execAlterNavigation(ctx, 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 execCreateImageCollection(ctx, 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 execDropImageCollection(ctx, 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 execCreateWorkflow(ctx, 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 execDropWorkflow(ctx, 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 execAlterWorkflow(ctx, 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 createBusinessEventService(ctx, 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 dropBusinessEventService(ctx, 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 alterSettings(ctx, 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 createConfiguration(ctx, 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 dropConfiguration(ctx, 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 createODataClient(ctx, 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 alterODataClient(ctx, 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 dropODataClient(ctx, 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 createODataService(ctx, 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 alterODataService(ctx, 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 dropODataService(ctx, 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 execCreateJsonStructure(ctx, 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 execDropJsonStructure(ctx, 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 execCreateImportMapping(ctx, 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 execDropImportMapping(ctx, 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 execCreateExportMapping(ctx, 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 execDropExportMapping(ctx, 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 createRestClient(ctx, 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 dropRestClient(ctx, 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 execCreatePublishedRestService(ctx, 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 execDropPublishedRestService(ctx, 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 execAlterPublishedRestService(ctx, 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 execCreateExternalEntity(ctx, 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 createExternalEntities(ctx, 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 execGrantODataServiceAccess(ctx, 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 execRevokeODataServiceAccess(ctx, 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 execGrantPublishedRestServiceAccess(ctx, 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 execRevokePublishedRestServiceAccess(ctx, 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 execCreateDataTransformer(ctx, 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 execDropDataTransformer(ctx, 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 execShow(ctx, 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 execShowWidgets(ctx, 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 execUpdateWidgets(ctx, 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 execCatalogQuery(ctx, 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 execDescribe(ctx, 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 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. - 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 execShowFeatures(ctx, 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 execShowDesignProperties(ctx, 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 execDescribeStyling(ctx, 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 execAlterStyling(ctx, 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 execUpdate(ctx) }) - 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 execRefresh(ctx) }) - 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 execRefreshCatalogStmt(ctx, 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 execSearch(ctx, 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 execSet(ctx, 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 execHelp(ctx) }) - 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 execExit(ctx) }) - 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 execExecuteScript(ctx, 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 execLint(ctx, 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 execAlterPage(ctx, 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 execDefineFragment(ctx, 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 describeFragmentFrom(ctx, 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 execSQLConnect(ctx, 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 execSQLDisconnect(ctx, 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 execSQLConnections(ctx) }) - 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 execSQLQuery(ctx, 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 execSQLShowTables(ctx, 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 execSQLShowViews(ctx, 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 execSQLShowFunctions(ctx, 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 execSQLDescribeTable(ctx, 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 execSQLGenerateConnector(ctx, 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 execImport(ctx, 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()) 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))