-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathsql-analyze.ts
More file actions
119 lines (111 loc) · 4.55 KB
/
sql-analyze.ts
File metadata and controls
119 lines (111 loc) · 4.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import z from "zod"
import { Tool } from "../../tool/tool"
import { Dispatcher } from "../native"
import type { Telemetry } from "../telemetry"
import type { SqlAnalyzeResult } from "../native/types"
// altimate_change start — progressive disclosure suggestions
import { PostConnectSuggestions } from "./post-connect-suggestions"
// altimate_change end
export const SqlAnalyzeTool = Tool.define("sql_analyze", {
description:
"Analyze SQL for anti-patterns, performance issues, and optimization opportunities. Performs lint, semantic, and safety analysis without executing the query. Provide schema_context or schema_path for accurate semantic analysis — without schema, table/column references cannot be resolved.",
parameters: z.object({
sql: z.string().describe("SQL query to analyze"),
dialect: z
.string()
.optional()
.default("snowflake")
.describe("SQL dialect (snowflake, postgres, bigquery, duckdb, etc.)"),
schema_path: z.string().optional().describe("Path to YAML/JSON schema file for table/column resolution"),
schema_context: z
.record(z.string(), z.any())
.optional()
.describe('Inline schema definition, e.g. {"table_name": {"col": "TYPE"}}'),
}),
async execute(args, ctx) {
const hasSchema = !!(args.schema_path || (args.schema_context && Object.keys(args.schema_context).length > 0))
try {
const result = await Dispatcher.call("sql.analyze", {
sql: args.sql,
dialect: args.dialect,
schema_path: args.schema_path,
schema_context: args.schema_context,
})
// The handler returns success=true when analysis completes (issues are
// reported via issues/issue_count). Only treat it as a failure when
// there's an actual error (e.g. parse failure).
const isRealFailure = !!result.error
// altimate_change start — sql quality findings for telemetry
const findings: Telemetry.Finding[] = (result.issues ?? []).map((issue) => ({
category: issue.rule ?? issue.type ?? "analysis_issue",
}))
// altimate_change end
// altimate_change start — progressive disclosure suggestions
let output = formatAnalysis(result)
const suggestion = PostConnectSuggestions.getProgressiveSuggestion("sql_analyze")
if (suggestion) {
output += "\n\n" + suggestion
PostConnectSuggestions.trackSuggestions({
suggestionType: "progressive_disclosure",
suggestionsShown: ["schema_inspect"],
warehouseType: "unknown",
})
}
// altimate_change end
return {
title: `Analyze: ${result.error ? "ERROR" : `${result.issue_count ?? 0} issue${(result.issue_count ?? 0) !== 1 ? "s" : ""}`} [${result.confidence ?? "unknown"}]`,
metadata: {
success: !isRealFailure,
issueCount: result.issue_count,
confidence: result.confidence,
dialect: args.dialect,
has_schema: hasSchema,
...(result.error && { error: result.error }),
...(findings.length > 0 && { findings }),
},
output,
}
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
return {
title: "Analyze: ERROR",
metadata: {
success: false,
issueCount: 0,
confidence: "unknown",
dialect: args.dialect,
has_schema: hasSchema,
error: msg,
},
output: `Failed to analyze SQL: ${msg}\n\nCheck your connection configuration and try again.`,
}
}
},
})
function formatAnalysis(result: SqlAnalyzeResult): string {
if (result.error) {
return `Analysis failed: ${result.error}`
}
const issues = result.issues ?? []
if (issues.length === 0) {
return "No anti-patterns or issues detected."
}
const issueCount = result.issue_count ?? issues.length
const lines: string[] = [
`Found ${issueCount} issue${issueCount !== 1 ? "s" : ""} (confidence: ${result.confidence ?? "unknown"}):`,
]
const factors = result.confidence_factors ?? []
if (factors.length > 0) {
lines.push(` Note: ${factors.join("; ")}`)
}
lines.push("")
for (const issue of issues) {
const loc = issue.location ? ` — ${issue.location}` : ""
const conf = issue.confidence !== "high" ? ` [${issue.confidence ?? "unknown"} confidence]` : ""
lines.push(` [${String(issue.severity ?? "unknown").toUpperCase()}] ${issue.type ?? "unknown"}${conf}`)
lines.push(` ${issue.message ?? ""}${loc}`)
lines.push(` → ${issue.recommendation ?? ""}`)
lines.push("")
}
return lines.join("\n")
}