(args: z.infer<typeof DbQueryArgs>, _ctx: ToolContext)
| 217 | argsSchema = DbQueryArgs; |
| 218 | |
| 219 | async execute(args: z.infer<typeof DbQueryArgs>, _ctx: ToolContext): Promise<ToolResult> { |
| 220 | if (!args.allow_write && !READ_ONLY_PREFIXES.test(args.sql)) { |
| 221 | return { |
| 222 | content: `[DB_QUERY_REFUSED] Only read statements (SELECT/EXPLAIN/SHOW/DESCRIBE/PRAGMA/WITH) are allowed by default. Detected non-read prefix in: ${args.sql.slice(0, 80)}…\n\nPass allow_write=true if intentional. Note: changes to a remote DB cannot be /restored by QodeX.`, |
| 223 | isError: true, |
| 224 | }; |
| 225 | } |
| 226 | |
| 227 | const parsed = parseConn(args.connection_string); |
| 228 | if (!parsed) return { content: `[DB_QUERY_ERROR] Bad connection_string`, isError: true }; |
| 229 | const g = await getConnection(parsed); |
| 230 | if (g.kind === 'error') return { content: `[DB_QUERY_ERROR] ${g.message}`, isError: true }; |
| 231 | const { conn } = g; |
| 232 | const maxRows = args.max_rows ?? 200; |
| 233 | |
| 234 | try { |
| 235 | let rows: any[] = []; |
| 236 | let affectedRows: number | undefined; |
| 237 | |
| 238 | if (parsed.dialect === 'mysql') { |
| 239 | const [r] = await conn.execute(args.sql, args.params ?? []); |
| 240 | if (Array.isArray(r)) rows = r as any[]; |
| 241 | else affectedRows = (r as any).affectedRows; |
| 242 | } else if (parsed.dialect === 'postgres') { |
| 243 | const r = await conn.query(args.sql, args.params ?? []); |
| 244 | rows = r.rows ?? []; |
| 245 | affectedRows = r.rowCount ?? undefined; |
| 246 | } else { |
| 247 | const stmt = conn.prepare(args.sql); |
| 248 | if (stmt.reader) { |
| 249 | rows = stmt.all(...(args.params ?? [])); |
| 250 | } else { |
| 251 | const info = stmt.run(...(args.params ?? [])); |
| 252 | affectedRows = info.changes; |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | const out: string[] = []; |
| 257 | out.push(`# DB Query result`); |
| 258 | out.push(`SQL: ${args.sql.slice(0, 200)}${args.sql.length > 200 ? '…' : ''}`); |
| 259 | if (affectedRows !== undefined) { |
| 260 | out.push(`Affected rows: ${affectedRows}`); |
| 261 | } |
| 262 | if (rows.length > 0) { |
| 263 | const total = rows.length; |
| 264 | const shown = rows.slice(0, maxRows); |
| 265 | const cols = Object.keys(shown[0]!); |
| 266 | // Pretty-print as a markdown table |
| 267 | out.push(''); |
| 268 | out.push(`Rows: ${total}${total > maxRows ? ` (showing first ${maxRows})` : ''}`); |
| 269 | out.push(''); |
| 270 | out.push('| ' + cols.join(' | ') + ' |'); |
| 271 | out.push('|' + cols.map(() => '---').join('|') + '|'); |
| 272 | for (const r of shown) { |
| 273 | out.push('| ' + cols.map(c => { |
| 274 | const v = (r as any)[c]; |
| 275 | if (v === null) return 'NULL'; |
| 276 | const s = typeof v === 'object' ? JSON.stringify(v) : String(v); |
nothing calls this directly
no test coverage detected