(args: z.infer<typeof DbSchemaArgs>, _ctx: ToolContext)
| 127 | argsSchema = DbSchemaArgs; |
| 128 | |
| 129 | async execute(args: z.infer<typeof DbSchemaArgs>, _ctx: ToolContext): Promise<ToolResult> { |
| 130 | const parsed = parseConn(args.connection_string); |
| 131 | if (!parsed) return { content: `[DB_SCHEMA_ERROR] Could not parse connection_string. Expected mysql://, postgres://, or sqlite:///path`, isError: true }; |
| 132 | const g = await getConnection(parsed); |
| 133 | if (g.kind === 'error') return { content: `[DB_SCHEMA_ERROR] ${g.message}`, isError: true }; |
| 134 | const { conn } = g; |
| 135 | |
| 136 | try { |
| 137 | const out: string[] = []; |
| 138 | out.push(`# DB Schema: ${parsed.dialect}${parsed.database ? ` / ${parsed.database}` : ''}${parsed.filepath ? ` / ${parsed.filepath}` : ''}`); |
| 139 | |
| 140 | if (!args.table) { |
| 141 | let tables: any[]; |
| 142 | if (parsed.dialect === 'mysql') { |
| 143 | const [rows] = await conn.execute('SHOW TABLES'); |
| 144 | tables = (rows as any[]).map(r => Object.values(r)[0]); |
| 145 | } else if (parsed.dialect === 'postgres') { |
| 146 | const r = await conn.query(`SELECT tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY tablename`); |
| 147 | tables = r.rows.map((r: any) => r.tablename); |
| 148 | } else { |
| 149 | tables = conn.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`).all().map((r: any) => r.name); |
| 150 | } |
| 151 | out.push(`Tables (${tables.length}):`); |
| 152 | for (const t of tables) out.push(` - ${t}`); |
| 153 | } else { |
| 154 | // Table-specific detail |
| 155 | if (parsed.dialect === 'mysql') { |
| 156 | const [cols] = await conn.execute(`SHOW FULL COLUMNS FROM \`${args.table.replace(/`/g, '')}\``); |
| 157 | out.push(`## Columns`); |
| 158 | for (const c of cols as any[]) out.push(` ${c.Field.padEnd(28)} ${String(c.Type).padEnd(20)} ${c.Null === 'NO' ? 'NOT NULL' : 'NULL '} ${c.Key || ''} ${c.Default !== null ? `DEFAULT ${c.Default}` : ''}`); |
| 159 | const [idx] = await conn.execute(`SHOW INDEX FROM \`${args.table.replace(/`/g, '')}\``); |
| 160 | if ((idx as any[]).length > 0) { |
| 161 | out.push(''); |
| 162 | out.push(`## Indexes`); |
| 163 | for (const i of idx as any[]) out.push(` ${i.Key_name.padEnd(28)} on ${i.Column_name}${i.Non_unique === 0 ? ' (UNIQUE)' : ''}`); |
| 164 | } |
| 165 | } else if (parsed.dialect === 'postgres') { |
| 166 | const colsR = await conn.query(` |
| 167 | SELECT column_name, data_type, is_nullable, column_default |
| 168 | FROM information_schema.columns WHERE table_name = $1 |
| 169 | ORDER BY ordinal_position`, [args.table]); |
| 170 | out.push(`## Columns`); |
| 171 | for (const c of colsR.rows) out.push(` ${c.column_name.padEnd(28)} ${String(c.data_type).padEnd(20)} ${c.is_nullable === 'NO' ? 'NOT NULL' : 'NULL '} ${c.column_default !== null ? `DEFAULT ${c.column_default}` : ''}`); |
| 172 | const idxR = await conn.query(`SELECT indexname, indexdef FROM pg_indexes WHERE tablename = $1`, [args.table]); |
| 173 | if (idxR.rows.length > 0) { |
| 174 | out.push(''); |
| 175 | out.push(`## Indexes`); |
| 176 | for (const i of idxR.rows) out.push(` ${i.indexname}: ${i.indexdef}`); |
| 177 | } |
| 178 | } else { |
| 179 | const cols = conn.prepare(`PRAGMA table_info("${args.table.replace(/"/g, '')}")`).all(); |
| 180 | out.push(`## Columns`); |
| 181 | for (const c of cols as any[]) out.push(` ${c.name.padEnd(28)} ${String(c.type).padEnd(20)} ${c.notnull ? 'NOT NULL' : 'NULL '} ${c.dflt_value ? `DEFAULT ${c.dflt_value}` : ''}${c.pk ? ' PK' : ''}`); |
| 182 | const idx = conn.prepare(`PRAGMA index_list("${args.table.replace(/"/g, '')}")`).all(); |
| 183 | if ((idx as any[]).length > 0) { |
| 184 | out.push(''); |
| 185 | out.push(`## Indexes`); |
| 186 | for (const i of idx as any[]) out.push(` ${i.name}${i.unique ? ' (UNIQUE)' : ''}`); |
nothing calls this directly
no test coverage detected