Skip to main content
Glama
IBM
by IBM
logic.ts8.08 kB
/** * @fileoverview Defines the core logic, schemas, and types for the `generate_sql` tool. * This module is the single source of truth for the tool's data contracts (Zod schemas) * and its pure business logic for generating SQL DDL statements from IBM i database objects. * @module src/mcp-server/tools/generateSql/logic * @see {@link src/mcp-server/tools/generateSql/registration.ts} for the handler and registration logic. */ import { z } from "zod"; import { JsonRpcErrorCode, McpError } from "../../../types-global/errors.js"; import { getRequestContext } from "../../../utils/index.js"; import { logger } from "../../../utils/internal/logger.js"; import { IBMiConnectionPool } from "../../services/connectionPool.js"; /** * Supported IBM i database object types for DDL generation. * These correspond to the valid values for the DATABASE_OBJECT_TYPE parameter * of the QSYS2.GENERATE_SQL stored procedure. */ const OBJECT_TYPES = [ "ALIAS", "CONSTRAINT", "FUNCTION", "INDEX", "MASK", "PERMISSION", "PROCEDURE", "SCHEMA", "SEQUENCE", "TABLE", "TRIGGER", "TYPE", "VARIABLE", "VIEW", "XSR", ] as const; /** * Zod schema defining the input parameters for the `generate_sql` tool. */ export const GenerateSqlInputSchema = z .object({ object_name: z .string() .min(1, "Object name cannot be empty.") .max(128, "Object name cannot exceed 128 characters.") .describe("The name of the IBM i database object to generate DDL for."), object_library: z .string() .min(1, "Library name cannot be empty.") .max(128, "Library name cannot exceed 128 characters.") .default("QSYS2") .describe( "The library where the database object is located. Defaults to QSYS2.", ), object_type: z .enum(OBJECT_TYPES) .default("TABLE") .describe( "The type of database object to generate DDL for. Valid types include TABLE, VIEW, INDEX, PROCEDURE, FUNCTION, etc.", ), }) .describe("Input schema for generating SQL DDL"); /** * Zod schema for the successful response of the `generate_sql` tool. * Contains the generated DDL along with metadata about the source object. */ export const GenerateSqlOutputSchema = z .object({ sql: z .string() .min(1, "Generated SQL cannot be empty.") .describe( "The generated DDL SQL statements for the specified database object.", ), object_name: z .string() .describe("The name of the object the DDL was generated for."), object_library: z .string() .describe("The library of the object the DDL was generated for."), object_type: z .string() .describe("The type of the object the DDL was generated for."), }) .describe("Output schema for generated SQL DDL"); /** * TypeScript type inferred from the input schema. * Represents the validated parameters passed to the generate SQL logic. */ export type GenerateSqlInput = z.infer<typeof GenerateSqlInputSchema>; /** * TypeScript type inferred from the output schema. * Represents the structured response returned by the generate SQL logic. */ export type GenerateSqlOutput = z.infer<typeof GenerateSqlOutputSchema>; /** * Processes the core logic for the `generate_sql` tool. * This function calls the QSYS2.GENERATE_SQL procedure to generate DDL for IBM i database objects. * * @param params - The validated input parameters containing object details * @returns A promise resolving with the generated SQL DDL * @throws {McpError} If the SQL generation encounters an unrecoverable issue */ export async function generateSqlLogic( params: GenerateSqlInput, ): Promise<GenerateSqlOutput> { const context = getRequestContext(); logger.debug( { ...context, toolInput: params }, "Processing generate SQL DDL logic.", ); const sql = `CALL QSYS2.GENERATE_SQL( DATABASE_OBJECT_NAME => ?, DATABASE_OBJECT_LIBRARY_NAME => ?, DATABASE_OBJECT_TYPE => ?, CREATE_OR_REPLACE_OPTION => '1', PRIVILEGES_OPTION => '0', STATEMENT_FORMATTING_OPTION => '0', SOURCE_STREAM_FILE_END_OF_LINE => 'LF', SOURCE_STREAM_FILE_CCSID => 1208 )`; const startTime = Date.now(); try { // Execute the GENERATE_SQL procedure using pagination to get all results const result = await IBMiConnectionPool.executeQueryWithPagination( sql, [params.object_name, params.object_library, params.object_type], context, 500, // Fetch 500 rows at a time ); const executionTime = Date.now() - startTime; if (!result.success) { throw new McpError( JsonRpcErrorCode.DatabaseError, "SQL DDL generation failed", { objectName: params.object_name, objectLibrary: params.object_library, objectType: params.object_type, sqlReturnCode: result.sql_rc, executionTime, }, ); } // Process the result data to extract the generated DDL let generatedSql = ""; if (result.data && Array.isArray(result.data)) { logger.debug( { ...context, totalRows: result.data.length, firstRowKeys: result.data[0] ? Object.keys(result.data[0]) : [], }, "Processing GENERATE_SQL result data.", ); // Build the result string from the SRCDTA column // Results are already in correct order from the paginated query const resultStrings: string[] = []; for (const res of result.data) { if (res && typeof res === "object" && "SRCDTA" in res) { const srcData = (res as Record<string, unknown>).SRCDTA; if (srcData && typeof srcData === "string") { resultStrings.push(srcData); } } } generatedSql = resultStrings.join("\n"); logger.debug( { ...context, processedRows: resultStrings.length, totalDdlLength: generatedSql.length, avgRowLength: resultStrings.length > 0 ? Math.round(generatedSql.length / resultStrings.length) : 0, }, "DDL extraction completed.", ); } if (!generatedSql || generatedSql.trim().length === 0) { throw new McpError( JsonRpcErrorCode.DatabaseError, "No SQL DDL generated for the specified object", { objectName: params.object_name, objectLibrary: params.object_library, objectType: params.object_type, resultRowCount: result.data?.length || 0, }, ); } const response: GenerateSqlOutput = { sql: generatedSql.trim(), object_name: params.object_name, object_library: params.object_library, object_type: params.object_type, }; logger.debug( { ...context, objectName: params.object_name, objectLibrary: params.object_library, objectType: params.object_type, sqlLength: response.sql.length, executionTime, rowCount: result.data?.length || 0, }, "SQL DDL generated successfully.", ); return response; } catch (error) { const executionTime = Date.now() - startTime; logger.error( { ...context, error: error instanceof Error ? error.message : String(error), objectName: params.object_name, objectLibrary: params.object_library, objectType: params.object_type, executionTime, }, "SQL DDL generation failed.", ); // Re-throw McpErrors as-is, wrap other errors if (error instanceof McpError) { throw error; } throw new McpError( JsonRpcErrorCode.DatabaseError, `SQL DDL generation failed: ${error instanceof Error ? error.message : String(error)}`, { objectName: params.object_name, objectLibrary: params.object_library, objectType: params.object_type, executionTime, originalError: error instanceof Error ? error.name : "Unknown", }, ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IBM/ibmi-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server