JAMYPG NL2SQL MCP
Go-based MCP server for metadata-grounded NL2SQL over PostgreSQL, MySQL, and
MariaDB. Current source version: v0.58.0 (converted from the Oracle-based
jasql project).
The server loads JSON metadata from a dataset directory (e.g. data/metadb,
data/sakila), compiles it into an in-memory catalog, search index, join
graph, prompt registry, and static SQL guardrails, then exposes them through
MCP tools, resources, and prompts. Generated SQL can be executed read-only
against any of the three target engines through pure-Go drivers โ no CGO, no
client libraries, no build tags.
Built with Codex and GPT-5.6
JAMYPG was developed through a human-directed AI engineering workflow using OpenAI Codex and GPT-5.6.
How Codex Was Used
Codex served as the primary implementation agent throughout the project. It was used to:
- Explore and understand the existing Go codebase
- Implement MCP tools, resources, prompts, and transport behavior
- Develop metadata catalog, search, and join-graph features
- Add PostgreSQL, MySQL, and MariaDB connectivity
- Implement SQL validation and read-only execution guardrails
- Create REST APIs, administration features, and integration tests
- Refactor duplicated code and improve error handling
- Update technical documentation alongside source-code changes
Development was performed iteratively. Each task was defined with explicit goals and constraints, and Codex generated or modified the relevant code. The resulting changes were then reviewed, tested, and refined before being accepted.
How GPT-5.6 Was Used
GPT-5.6 was used as the architecture, reasoning, and review layer of the development process. It helped with:
- Designing the metadata-grounded NL2SQL architecture
- Defining safe and explainable SQL-generation workflows
- Identifying schema-hallucination and incorrect-join risks
- Designing clarification, validation, and query-execution stages
- Reviewing MCP client compatibility and session behavior
- Developing multi-database abstraction strategies
- Creating test scenarios and evaluation criteria
- Reviewing security, maintainability, and enterprise-readiness
- Improving project documentation and presentation materials
GPT-5.6 was particularly useful for reasoning across multiple system concerns at once, including metadata quality, SQL dialect differences, MCP protocol behavior, database security, and LLM reliability.
Human Oversight
AI-generated changes were not accepted automatically. The project owner remained responsible for:
- Defining product goals and technical requirements
- Reviewing generated code and architectural decisions
- Running unit and integration tests
- Verifying SQL safety rules
- Evaluating generated queries against expected results
- Approving the final implementation
This combination allowed Codex to accelerate implementation while GPT-5.6 supported architectural reasoning and systematic review, with human judgment controlling the final result.
๐ ์์ธ ๋ฌธ์: docs/README.md โ ์ํคํ ์ฒ, MCP ๋๊ตฌ ๋ ํผ๋ฐ์ค(85์ข ), SQL ์์ฑ ์ํฌํ๋ก, ๊ฒ์ฆ ๋ฃฐ ์นดํ๋ก๊ทธ(33์ข ), ๋ฐ์ดํฐ์ ๊ฐ์ด๋(18์ข ), REST API, DB ์ปค๋ฅํฐ, ์ด์/ํ๊ฐ/๋ณด์/๊ฐ๋ฐ์ ๊ฐ์ด๋.
Quick Start
HTTP ๋ชจ๋ ๊ธฐ๋ณธ ์ง์ ์ :
- MCP endpoint:
http://127.0.0.1:9797/mcp - Web admin:
http://127.0.0.1:9797/admin - Swagger UI:
http://127.0.0.1:9797/docs - Health check:
http://127.0.0.1:9797/healthz
Supported Target Databases
connect_string์ host:port/dbname ์ถ์ฝํ, postgres:///mysql:// URL,
go-sql-driver DSN์ ๋ชจ๋ ํ์ฉํฉ๋๋ค. ์์ฑ SQL์ ๋ฐฉ์ธ์ ๋ฐ์ดํฐ์
์
databases.json(dbms) ๋๋ overrides.json(dialect)์ด ๊ฒฐ์ ํ๋ฉฐ ๊ธฐ๋ณธ์
postgres์
๋๋ค. ์์ธ: docs/db-connector.md.
NL2SQL Recommended Flow
๋๋ถ๋ถ์ ์ง๋ฌธ์ ๊ฐ๋ณ ๋๊ตฌ๋ฅผ ์ฌ๋ฌ ๋ฒ ์ค์ผ์คํธ๋ ์ด์
ํ์ง ๋ง๊ณ
prepare_sql_context๋ถํฐ ํธ์ถํ์ธ์.
prepare_sql_context(question)ํธ์ถ- ์๋ต์ด
status: "needs_clarification"์ด๋ฉด SQL์ ๋ง๋ค์ง ๋ง๊ณclarifications์ ์ง๋ฌธ์ ์ฌ์ฉ์์๊ฒ ๋๋ฌป์ต๋๋ค. - ๋ต์ ๋ฐ์ ๋ค
prepare_sql_context(question, clarifications={...})๋ก ๋ค์ ํธ์ถํฉ๋๋ค. status: "ready"์ด๋ฉดskeleton.skeleton_sql์/* SLOT */๋ง ์ฑ์ SQL์ ์์ฑํฉ๋๋ค.validate_sqlโexplain_sqlโ ํ์ ์run_sql_safely์์๋ก ์งํํฉ๋๋ค.
์ด ํ๋ฆ์ ํ ์ด๋ธ/์ปฌ๋ผ/์งํ/์๊ฐ์กฐ๊ฑด/์กฐ์ธ ๊ฒฝ๋ก/๊ฒ์ฆ ํํธ๋ฅผ ํ ๋ฒ์ ๋ฌถ์ด LLM์ด ์คํค๋ง๋ฅผ ์ถ์ธกํ๊ฑฐ๋ ํ์ ๊ฒ์ฆ ๋จ๊ณ๋ฅผ ๊ฑด๋๋ฐ๋ ์ผ์ ์ค์ ๋๋ค.
Transports
stdio: newline-delimited JSON-RPC over standard input/output. Use this for desktop MCP clients that launch a local subprocess.http: Streamable HTTP at a single MCP endpoint. Use this for local HTTP clients, gateways, or remote service wrapping.
Build
์์ Go ๋น๋ ํ๋๋ก ์ธ DB ๋ชจ๋ ์ง์ํฉ๋๋ค (CGO ๋ถํ์, ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ถํ์):
Windows PowerShell:
.\scripts\build.ps1
Linux/macOS shell:
sh ./scripts/build.sh
Artifacts:
dist/jamypg-mcp-windows-amd64.exe
dist/jamypg-mcp-linux-amd64
dist/jamypg-mcp-linux-arm64
Single-platform builds:
go build -o .\bin\jamypg-mcp.exe .\cmd\jamypg-mcp
go build -o ./bin/jamypg-mcp ./cmd/jamypg-mcp
Docker Image
๋จ์ผ Dockerfile์ด ์คํ ๊ฐ๋ฅํ ์์ ํ ์ด๋ฏธ์ง๋ฅผ ๋ง๋ญ๋๋ค (๊ณผ๊ฑฐ์
Dockerfile.oracle/Instant Client ์ ์ฐจ๋ ์ ๊ฑฐ๋์์ต๋๋ค):
docker build -t jamypg-mcp:v0.58.0 .
docker run --rm -p 9797:9797 \
-e JAMYPG_ADMIN_TOKEN=change-me \
-e PG_PROD_PW=... \
jamypg-mcp:v0.58.0
DB ํ๋กํ์ผ์ /admin/db ๋๋ DB profile REST/MCP API๋ก ๊ตฌ์ฑํ ๋ค
run_sql_safely๋ก read-only ์คํํฉ๋๋ค. See
docs/db-connector.md.
Integration Test Environment (pg + mysql + mariadb)
jamypg์ ๋ฉํ DB ์คํค๋ง ์์ฒด๋ฅผ text2sql ๋์์ผ๋ก ์ธ ์์ง์ ์ ์ฌํ ํ ์คํธ ํ๊ฒฝ์ด ํฌํจ๋์ด ์์ต๋๋ค:
docker compose -f deploy/test/docker-compose.yml up -d
# postgres:16 โ 127.0.0.1:55432 (db jamypg_meta; ๋ฉํ DB ๊ฒธ ๋์ DB)
# mysql:8.4 โ 127.0.0.1:53306 (database `public`)
# mariadb:11.4 โ 127.0.0.1:53307 (database `public`)
go test -tags integration ./test/integration -v # ping/guard/limit/explain/
# ์ค๋ฅ์ฝ๋/text2sql ๊ณจ๋ ์
8์ข
ร 3๊ฐ DB
# ์๋ฒ๋ฅผ ์ด ๋ฐ์ดํฐ์
์ผ๋ก ์ง์ ๋์๋ณด๊ธฐ
go run ./cmd/jamypg-mcp -data data/metadb -addr 127.0.0.1:9797
# (์ ํ) ๋ฉํ DB ๋ชจ๋: -meta-db 'postgres://postgres:[email protected]:55432/jamypg_meta'
์นดํ๋ก๊ทธ ๋ฐ์ดํฐ์
์ data/metadb/(๋ฌผ๋ฆฌ/๋
ผ๋ฆฌ ๋ชจ๋ธ, ๊ด๊ณ, ์ฉ์ด์ง, ์งํ/์ฝ๋
์ฌ์ , ์ปฌ๋ผ ํต๊ณ, ์์ SQL, ๊ณจ๋ ์
, ํ๋กํ์ผ 3์ข
)์ด๋ฉฐ
python3 deploy/test/gen_testenv.py๋ก ์ฌ์์ฑํฉ๋๋ค.
์ ๋ช ์คํ์์ค ์คํค๋ง ๋ฐ์ดํฐ์ (sakila / northwind / wordpress)
๊ฐ์ ์ปจํ
์ด๋์ ์ ๋ช
์คํ์์ค ์๋น์ค ์คํค๋ง 3์ข
์ด ์๋๋์ด ์๊ณ , ๊ฐ๊ฐ ๋
๋ฆฝ
๋ฐ์ดํฐ์
์ผ๋ก text2sql์ ๊ฒ์ฆํฉ๋๋ค (python3 deploy/test/gen_oss_testenv.py๋ก
์ฌ์์ฑ):
์ธ ์คํค๋ง ๋ชจ๋ PostgreSQL(์คํค๋ง)ยทMySQL/MariaDB(๋๋ช
๋ฐ์ดํฐ๋ฒ ์ด์ค)์ ๋์ผํ๊ฒ
์ ์ฌ๋์ด sakila.film ๊ฐ์ ์คํค๋ง ํ์ SQL์ด ์ธ ์์ง์์ ๊ทธ๋๋ก ์คํ๋๊ณ ,
๊ณจ๋ ์
์ ๊ธฐ๋ ์ ๋ต(์: ์นดํ
๊ณ ๋ฆฌ๋ณ ์ํ ์ 1์)์ด ์ธ ์์ง์์ ์ผ์นํ๋์ง๊น์ง
ํตํฉ ํ
์คํธ๊ฐ ๊ฒ์ฆํฉ๋๋ค:
go run ./cmd/jamypg-mcp -data data/sakila -addr 127.0.0.1:9797 # ํ๋กํ์ผ: pg-sakila / mysql-sakila / mariadb-sakila
Run With stdio
Windows:
.\dist\jamypg-mcp-windows-amd64.exe -transport stdio -data .\data\metadb
Linux:
chmod +x ./dist/jamypg-mcp-linux-amd64
./dist/jamypg-mcp-linux-amd64 -transport stdio -data ./data/metadb
Example MCP client config for Windows:
{
"mcpServers": {
"jamypg": {
"command": "C:\\Users\\USER\\projects\\jamypg\\dist\\jamypg-mcp-windows-amd64.exe",
"args": ["-transport", "stdio", "-data", "C:\\Users\\USER\\projects\\jamypg\\data\\metadb"]
}
}
}
Example MCP client config for Linux:
{
"mcpServers": {
"jamypg": {
"command": "/opt/jamypg/dist/jamypg-mcp-linux-amd64",
"args": ["-transport", "stdio", "-data", "/opt/jamypg/data/metadb"]
}
}
}
stdio smoke test:
$msg = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}'
$msg | .\dist\jamypg-mcp-windows-amd64.exe -transport stdio -data .\data\metadb
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}' \
| ./dist/jamypg-mcp-linux-amd64 -transport stdio -data ./data/metadb
Run With Streamable HTTP
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -addr 127.0.0.1:9797
MCP endpoint:
http://127.0.0.1:9797/mcp
Health check:
Invoke-RestMethod http://127.0.0.1:9797/healthz
MCP Transport
This implements the MCP Streamable HTTP transport:
POST /mcpaccepts one JSON-RPC MCP message.GET /mcpopens a Server-Sent Events stream for server-to-client messages.DELETE /mcpcloses a stateful session.Mcp-Session-Idis issued afterinitializebut never required: clients that do not echo the header (qwen-code, opencode, ...) are served normally (lenient session policy).MCP-Protocol-Version: 2025-06-18is accepted on subsequent requests.- Origin validation allows empty origins,
localhost,127.0.0.1, and::1.
For stateless local testing:
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -stateless
To return POST responses as SSE:
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -sse-post
Curl Smoke Test
Initialize:
$init = @{
jsonrpc = "2.0"
id = 1
method = "initialize"
params = @{
protocolVersion = "2025-06-18"
capabilities = @{}
clientInfo = @{ name = "curl"; version = "0.0.1" }
}
} | ConvertTo-Json -Depth 10
$res = Invoke-WebRequest `
-Uri http://127.0.0.1:9797/mcp `
-Method POST `
-ContentType "application/json" `
-Headers @{ Accept = "application/json, text/event-stream" } `
-Body $init
$sid = $res.Headers["Mcp-Session-Id"]
$res.Content
List tools:
$body = @{
jsonrpc = "2.0"
id = 2
method = "tools/list"
} | ConvertTo-Json -Depth 5
Invoke-RestMethod `
-Uri http://127.0.0.1:9797/mcp `
-Method POST `
-ContentType "application/json" `
-Headers @{
Accept = "application/json, text/event-stream"
"Mcp-Session-Id" = $sid
"MCP-Protocol-Version" = "2025-06-18"
} `
-Body $body
Search schema:
$body = @{
jsonrpc = "2.0"
id = 3
method = "tools/call"
params = @{
name = "search_schema"
arguments = @{
question = "์ต๊ทผ 6๊ฐ์๊ฐ ์ ์ฉ์นด๋ ์ด์ฉ ๋ด์ญ์ด ์๋ ๊ณ ๊ฐ ์"
top_k = 5
include_columns = $true
}
}
} | ConvertTo-Json -Depth 10
Invoke-RestMethod `
-Uri http://127.0.0.1:9797/mcp `
-Method POST `
-ContentType "application/json" `
-Headers @{
Accept = "application/json, text/event-stream"
"Mcp-Session-Id" = $sid
"MCP-Protocol-Version" = "2025-06-18"
} `
-Body $body
Tools
prepare_sql_contextโ ์ง๋ฌธ ๋ถ์โ๊ฒ์โ์งํโ์คํค๋งโ์กฐ์ธโSQL ๊ณจ๊ฒฉ์ ํ ๋ฒ์ ์์ฑํ๋ ๊ถ์ฅ ์ง์ ์ analyze_questionโ ์ง๋ฌธ ๋ถํด: intent, ์งํ(์ฌ์ ๋งค์นญ), ์ฐจ์, ํํฐ, ์๊ฐ๋ฒ์, ์ ๋ ฌ/limit, ๋ชจํธ์ฑ, ์ ์ฉ ๊ธฐ๋ณธ๊ฐretrieve_contextโ ๊ฒ์ ํ๋ณด์ ์กฐ์ธ ๊ทธ๋ํ ํ์ฅ์ ๊ฒฐํฉํ๊ณ ์ ์ ๊ทผ๊ฑฐยท์กฐ์ธ ๊ฒฝ๋กยท๊ฐ ์ฆ๊ฑฐ๋ฅผ ๋ฐํsearch_schemaโ ๋ค์ค ์ ํธ ์ค์ฝ์ด๋ง(๋ฌผ๋ฆฌ/๋ ผ๋ฆฌ๋ช , ์ค๋ช , ๋์์ด, ๋๋ฉ์ธ, ์งํ์ฌ์ , ์ํ๊ฐ, ๊ณผ๊ฑฐ ์ฑ๊ณต SQL, ์กฐ์ธ ์ฐ๊ฒฐ์ฑ) + ๋งค์นญ ์ฌ์ + ์ ์ธ ํ๋ณด/์ฌ์get_schema_contextโ ์์ถ ์ปจํ ์คํธ: ์ ํ ํ ์ด๋ธ/์ปฌ๋ผ Top-K, ํ์ ์กฐ์ธ ์กฐ๊ฑด, ์งํ ๊ณ์ฐ์, ์๊ฐ ์กฐ๊ฑด, PII ํ์, ์ ์ธ ์ปฌ๋ผ ๋ก๊ทธget_join_pathsโ ์กฐ์ธ ๊ทธ๋ํ ๊ธฐ๋ฐ ๊ฒฝ๋ก(๋ชจ๋ ์), confidence/preferred/caution, ์ ์ ๋ขฐยท๊ฒฝ๋ก์์ ๊ฐ์ด๋์ค, ๊ธ์ง ์กฐ์ธ ์ฐจ๋จget_metric_definitionโ ์งํ ์ฌ์ (metrics.json) ์ฐ์ ์กฐํ; exact/business name/alias์ glossaryยทํ ํฐ ๊ทผ์ ๋๋ฅผ ๊ฒฐํฉํ๊ณ confidence/evidence๋ฅผ ๋ฐํ, ์์ผ๋ฉด ์ถ์ ํ๋ณด๋ฅผ ๋ช ํํ ๋ถ๋ฆฌget_column_statsโ ๋ฉํ + ํ๋กํ์ผ ํต๊ณ(null ๋น์จ, distinct, min/max, top values, ํฌ๋งท ํจํด)find_filter_columnsโ ์ง๋ฌธ ์ ๋ฆฌํฐ๋ด ๊ฐ(์์ธ, ์ ์, ๊ฐ์ธ์ฌ์ ์...)์ ์ฝ๋์ฌ์ /top values๋ก ํํฐ ์ปฌ๋ผ์ ๋งคํresolve_timeโ ์๊ฐ ํํ(์ค๋/์ง๋๋ฌ/์ต๊ทผ 3๊ฐ์/2025๋ 6์/์๋ฐ๊ธฐ/์ ์ ๋๋น...)์ semantic_type๋ณ SQL ์กฐ๊ฑด์ผ๋ก ๋ณํsearch_examplesโ golden SQL ์์ ๊ฒ์ (์ง๋ฌธ์ intent ์๊ทธ๋์ฒ์ ์์ target_intent์ ๊ตฌ์กฐ ์ ์ฌ๋๋ก ๋ญํน โ ๊ฐ์ SQL ํํ์ ์์ ์ฐ์ )build_sql_skeletonโ ๋ณต์ก/๋ค์ค ํ ์ด๋ธ ์ง๋ฌธ์ฉ: ๊ฒ์ฆ๋ ๋ถํ(์นดํ๋ก๊ทธ ์กฐ์ธ ์กฐ๊ฑด+alias, ์งํ์ฌ์ expression, semantic_type๋ณ ์๊ฐ ์กฐ๊ฑด, ์ ์ฑ ํํฐ)์ ์กฐ๋ฆฝํ SQL ๊ณจ๊ฒฉ ๋ฐํ. LLM์/* SLOT */์ฃผ์๋ง ์ฑ์rank_candidatesโ ํ๋ณด SQL ์ฌ๋ฌ ๊ฐ๋ฅผ ์๋ฒ์ธก ๊ฐ๊ด ์ ํธ(๊ฒ์ฆ ์ค๋ฅ/๊ฒฝ๊ณ , ๋ฆฌ์คํฌ, ๊ฒฐ๊ณผ ์คํค๋ง ์ปค๋ฒ๋ฆฌ์ง, ์งํ ์ผ์น)๋ก ์ ๋ ฌํด ์ต์ ์ ๋ฐํ โ self-consistency๋ฅผ LLM ์๊ธฐํ๊ฐ ๋์ ๊ฐ๊ด ์ ์๋ก ๊ตฌํsuggest_joinsโ ๋จ์ผ ์ปฌ๋ผ PK ๋ง์คํฐ๋ฅผ ์ฐธ์กฐํ๋ ๋ฏธ์ฐ๊ฒฐ ํ ์ด๋ธ์ ๋ฐ๊ตดํด ์กฐ์ธ ์ฃ์ง ํ๋ณด ์ ์(FK/์ธ๋ฑ์ค/ํ์ /๋์์ถํ ๊ทผ๊ฑฐ + overrides.json ์ค๋ํซ). ์ด์์ ๊ฒํ ์ฉ โ ์๋ ์ ์ฉ๋์ง ์์suggest_join_relationsโ ๊ณจ๋ ์ ์์ ์กฐ์ธ ๊ฒฝ๋ก๊ฐ ๋๊ธด ํ ์ด๋ธ ์์ ์ฐพ๊ณ ๊ณตํต ํค ๊ธฐ๋ฐ relation ๋ณด๊ฐ ํ๋ณด๋ฅผ ์ ์validate_sqlโ ์ ์ ๊ฒ์ฆ: ๋ฏธ์กด์ฌ ํ ์ด๋ธ/์ปฌ๋ผ, ์กฐ์ธ ๊ทธ๋ํ, ์นดํฐ์ , GROUP BY, ๋ฐฉ์ธ(postgres/mysql/mariadb โ Oracle ์ ์ฉ ๋ฌธ๋ฒ ์ฐจ๋จ, ๊ต์ฐจ ๋ฐฉ์ธ ํจ์ ๊ฒฝ๊ณ ), ๋ ์ง ํ์ , PII, ์งํ์ ์ผ์น, ์ฝ๋์ฌ์ ๊ฐ ๊ฒ์ฆ(์กด์ฌํ์ง ์๋ ์ฝ๋ ๋ฆฌํฐ๋ด ์ฐจ๋จ), ๊ฒฐ๊ณผ ์คํค๋ง ๊ฒ์ฆ(expected_outputs๋ก ์๊ตฌ ์ฐจ์/์งํ ๋๋ฝ ๊ฐ์ง), CTE/์ธ๋ผ์ธ๋ทฐ ์ค์ฝํ ์ธ์, ๊ตฌ์กฐํ๋fix_hints(์ต๋ 2ํ ์๋์์ ๋ฃจํ์ฉ)explain_sqlโ ๋ฆฌ์คํฌ ์ถ์ : ์ ์ ๋ถ์ +profile์ง์ ์ ์ค์ธก EXPLAIN(postgresEXPLAIN (FORMAT JSON), mysql/mariadbEXPLAIN FORMAT=JSON) โ full scan/์นดํฐ์ /๋๋ ์ ๋ ฌ/๊ณ ๋น์ฉ ํ์ง, ๊ฐ์ ์ ์list_db_profilesโ ํธ์ถ์๊ฐ ์ฌ์ฉํ ์ ์๋ DB ์ฐ๊ฒฐ ํ๋กํ์ผ id์ ๋ง์คํน๋ ์ ์ยท์ ์ฑ ์ ๋ณด๋ฅผ ๋ฐํroute_db_profileโ ํ๋กํ์ผ์ด ๋ง์ ๋ SQL์ด ์ฐธ์กฐํ๋ ํ ์ด๋ธ์ ๋ฐฉ์ธ ํ์๋ก ์ถ์ถํด ๊ฐ ํ๋กํ์ผ์ ์ค์ธก ์ธ๋ฒคํ ๋ฆฌ(information_schema)ยท์ ์ธ ์คํค๋งยท๋ฐฉ์ธยทํฌ์คยท์ฐ์ ์์๋ก ์ ์ํํ์ฌ ์คํ ๋์ ํ๋กํ์ผ์ ํ์ . ๋ช ํํ ์น์๊ฐ ์์ผ๋ฉดdecisive=true๋กselected_profile์, ์ ๋งคํ๋ฉด ํ๋ณด ๋ชฉ๋ก์ ๋ฐํ.run_sql_safely(profile="auto")๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉrun_sql_safelyโ ๊ฒ์ฆ ํ ์ค์ DB ์คํ (profile์ง์ ์; postgres/mysql/mariadb, read-only ์ธ์ , ํ์์์ยทํ ์ ํยทtruncatedยท๊ฐ์ฌ ๋ก๊ทธ).profile="auto"๋ฉด router๊ฐ ๋์ ํ๋กํ์ผ์ ํ์ (์ ๋งคํ๋ฉดprofile_choice_required๋ก ์ฌ์ง๋ฌธ). ํ๋กํ์ผ ๋ฏธ์ง์ ์ dry-run ๊ฐ๋. ๊ฒ์ฆ ์คํจ SQL์ ์คํํ์ง ์์execute_with_repairโ ์๊ธฐ์์ ์คํ: ๊ฒ์ฆโ์คํโ์ง๋จ์ ํ ๋ฒ์ ์ํํ๊ณ ์คํจ ์repairํคํธ(์คํจ ๋จ๊ณ, ๋ถ๋ฅ๋ error_code+ํํธ, ์นดํ๋ก๊ทธ fix_hints, ์ฐธ์กฐ ํ ์ด๋ธ ์คํค๋ง)๋ฅผ ๋ฐํํด ํ ํด์ SQL ๊ต์ ๊ฐ๋ฅ. 0ํ์ด๋ฉดexecuted_empty+zero_row_hints. run_sql_safely์ ๋์ผ ๊ฐ๋, ๋ฐ๋ณต์ด ์์๋๋ฉด ์ด ๋๊ตฌ ์ฐ์list_metadata_sourcesโ ์๋ ๋ฉํ๋ฐ์ดํฐ ์์ง ์์ฒ์ผ๋ก ์ธ ์ ์๋ DB ํ๋กํ์ผ(source_id/name/type/๋ง์คํน ์ ์๋์) ๋ชฉ๋ก. ๋ฌผ๋ฆฌ ๋ฉํ๋ฐ์ดํฐ๋ ์๋ ์์งํ๋ ์ ๋ฌด ์๋ฏธ๋ ์น์ธ ๊ธฐ๋ฐ์ผ๋ก ๊ด๋ฆฌdiscover_metadataโ ์์ฒ DB์ ๋น์์คํ ์คํค๋ง ๋ชฉ๋ก ์กฐํ(information_schema๋ง ์ฝ๋ read-only). ์์ง ๋ฒ์ ์ง์ ์ฉdb_health_reportโ DBA ํฌ์ค ์ ๊ฒ: ์ฐ๊ฒฐ๋ ํ๋กํ์ผ DB์ ์์คํ ์นดํ๋ก๊ทธ๋ฅผ ์ฝ์ด PK ์๋ ํ ์ด๋ธ(high)ยท์ธ๋ฑ์ค ์๋ FK ์ปฌ๋ผ(medium)ยท๋ฏธ์ฌ์ฉ ์ธ๋ฑ์ค(low)ยทํต๊ณ ์ค๋๋จ/์์(medium)ยท๋ํ ํ ์ด๋ธ(์ฝ๋ฉํธ ์ฌ๋ถ, info)์ ์ง๋จ. PostgreSQL ์ ์ฒด, MySQL/MariaDB๋ ์ด์ ๊ฐ๋ฅ ํญ๋ชฉ๋ง. ์ฝ๊ธฐ ์ ์ฉ(์์ ยท์คํ ์์, ๊ฐ์ ์ DBA ๊ฒํ ํ)suggest_indexesโ ์ธ๋ฑ์ค ์ด๋๋ฐ์ด์ : ์ฟผ๋ฆฌ ๊ฐ์ฌ ๋ก๊ทธ(query-*.jsonl)์์ ๋๋ฆฐ ์ฑ๊ณต ์ฟผ๋ฆฌ๋ฅผ ๋ถ์ํด ์ธ๋ฑ์ค๊ฐ ์๋ WHERE/JOIN/ORDER BY ์ปฌ๋ผ์ ์ง๊ณํ๊ณ , ์ํฅ๋(๋ฐ์ ํ์ ร ํ๊ท ์ง์ฐ) ์์ผ๋ก ํ๋ณด ์ธ๋ฑ์ค๋ฅผ ์ ์. ๊ฐ ํ๋ณด์ ๊ฒํ ์ฉCREATE INDEXDDL๊ณผ ๋ํ ์ฟผ๋ฆฌ ํฌํจ. ์ฝ๊ธฐ ์ ์ฉยท๊ถ๊ณ ์ฉ(์๋ ์์ฑํ์ง ์์ผ๋ฉฐ DBA๊ฐ ์นด๋๋๋ฆฌํฐยท์ฐ๊ธฐ๋ถํ ๊ฒํ ํ ์ํ).profile(์ ํ)ยทmin_elapsed_ms(๊ธฐ๋ณธ 200)ยทdays(๊ธฐ๋ณธ 7)lint_sqlโ SQL ์ํฐํจํด ๋ฆฐํธ: ๋จ์ผ ๋ฌธ์ฅ์ ์ ์ ๋ถ์ํด ๊ณ ์ ์ ์ฑ๋ฅยท์ ํฉ์ฑ ์ค๋ฉ์ ์ง๋จ โSELECT *, ์ ๋ ์์ผ๋์นด๋LIKE '%โฆ',NOT IN (์๋ธ์ฟผ๋ฆฌ), ์ธ๋ฑ์ค ์ปฌ๋ผ์ ํจ์๋ก ๊ฐ์ผ ๋น-sargable ์กฐ๊ฑด, ์ธ๋ฑ์ค ์ปฌ๋ผ ๋ถ๋ฑํธ, ์ฝค๋ง ํฌ๋ก์ค ์กฐ์ธ,WHERE์OR,LIMIT์๋ORDER BY,WHERE์๋ DML. ๊ฐ ํญ๋ชฉ์ ์ฌ๊ฐ๋์ ๊ฐ์ ์ ์ ํฌํจ. ์นดํ๋ก๊ทธ ์ธ๋ฑ์ค ์ปค๋ฒ๋ฆฌ์ง ์ธ์ยท๊ถ๊ณ ์ฉ(์๋ ์์ ์ ํจ).sqlยทprofile(์ ํ)explain_sql_in_wordsโ SQL ์์ฐ์ด ์ค๋ช : SQL์ด ์ด๋ค ํ ์ด๋ธ(์นดํ๋ก๊ทธ ๋ ผ๋ฆฌ๋ช )์์ ๋ฌด์์ ํํฐยท์กฐ์ธยท๊ทธ๋ฃนยท์ ๋ ฌํ๊ณ ์ด๋ค ์ง๊ณ๋ฅผ ๊ณ์ฐํ๋์ง ํ๊ตญ์ด๋ก ์์ฝ. ์ ์ ๊ตฌ์กฐ ๋ถ์(์คํ ์ ํจ).sqlยทprofile(์ ํ)workload_reportโ ์ํฌ๋ก๋ ๋ฆฌํฌํธ: ๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๊ธฐ๊ฐ๋ณ๋ก ์ง๊ณํด ์ด/์ฑ๊ณต/์ค๋ฅ ๊ฑด์ยท์ค๋ฅ์จ, ์ง์ฐ ๋ถํฌ(avg/p50/p95/p99/max), ๋๋ฆฐ ์ฟผ๋ฆฌ ์, ๊ฐ์ฅ ๋ง์ด ์ ๊ทผํ ํ ์ด๋ธ, ์์ ์ค๋ฅ ์ฝ๋, ํดยทํ๋กํ์ผ๋ณ ์ฌ์ฉ๋, ๊ฐ์ฅ ๋๋ฆฐ ๋ฌธ์ฅ, ํผํฌ ์๊ฐ๋๋ฅผ ๋ฆฌํฌํธ. ์ฝ๊ธฐ ์ ์ฉ.profile(์ ํ)ยทdays(๊ธฐ๋ณธ 7)ยทslow_ms(๊ธฐ๋ณธ 200)get_dba_digestโ DBA ๋ค์ด์ ์คํธ: ์ํฌ๋ก๋ ๋ฆฌํฌํธ์ ์ธ๋ฑ์ค ์ด๋๋ฐ์ด์ ๋ฅผ ์์ถํ ๋ฅ๋ํ ์ด์ ์ค๋ ์ท โ ์ฟผ๋ฆฌ๋ยท์ค๋ฅ์จยทp95/์ต๋ ์ง์ฐยท๋๋ฆฐ ์ฟผ๋ฆฌ ์ยทํซ ํ ์ด๋ธยท์์ ์ธ๋ฑ์ค ํ๋ณด์ ํ ์ค ํค๋๋ผ์ธ. ์ฝ๊ธฐ ์ ์ฉ. ์ค์ผ์ค๋ฌ(-sync-interval+-digest-webhook+-dba-digest)๊ฐ ํฑ๋ง๋ค ์นํ ์ผ๋ก pushํ๋ ๊ฒ๊ณผ ๋์ผํ ๋ฐ์ดํฐ.profile(์ ํ)ยทdays(๊ธฐ๋ณธ 7)ยทslow_ms(๊ธฐ๋ณธ 200)
DBA ๊ด๋ฆฌ ๋๊ตฌ (privileged, dba/admin ์ญํ ์ ์ฉ)
ํ๋กํ์ผ์ DBA ์๊ฒฉ์ฆ๋ช
(db_profiles์ dba.enabled+dba.username+dba.password_ref)์ ์ค์ ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ฝ๊ธฐ ์ ์ฉ ์ฟผ๋ฆฌ ๊ณ์ ๊ณผ ๋ถ๋ฆฌ๋ ์ฐ๊ธฐ ๊ฐ๋ฅ ์ปค๋ฅ์
์ผ๋ก ์คํ๋๋ฉฐ, ๋ชจ๋ ๋ณ๊ฒฝ์ ๊ฐ์ฌ ๋ก๊ทธ(dba:*)์ ๊ธฐ๋ก๋ฉ๋๋ค. ๊ด๋ฆฌ ํ๋ฉด: /admin/dba-console.
dba_overviewโ ์ฝ์ ๊ฐ์: ๋ฐฉ์ธยทDBA ํ์ฑ ์ฌ๋ถยท์๋ฒ ๋ฒ์ ยท์ญํ /DB ์ (์ฝ๊ธฐ ์ ์ฉ)dba_list_usersโ ์ฌ์ฉ์/์ญํ ๋ชฉ๋ก๊ณผ ์์ฑ(superuserยทcreatedbยทcreateroleยทloginยท์ฐ๊ฒฐ์ ํ)dba_list_databasesโ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชฉ๋ก(์์ ์ยท์ธ์ฝ๋ฉยท์ฝ๋ ์ด์ ยทํฌ๊ธฐ)dba_list_settingsโ ์๋ฒ ์ค์ ํ๋ผ๋ฏธํฐ(pg_settings/global_variables), ์ด๋ฆ ๋ถ๋ถ๊ฒ์dba_list_sessionsโ ํ์ฑ ์ธ์ /๋ฐฑ์๋(pidยท์ฌ์ฉ์ยท์ํยท์ง์์๊ฐยทํ์ฌ ์ฟผ๋ฆฌ)dba_create_userโ ์ฌ์ฉ์/์ญํ ์์ฑ(postgres LOGIN/SUPERUSER/CREATEDB/CREATEROLE, mysqlCREATE USER). ๋น๋ฐ๋ฒํธ๋ ๊ฐ์ฌ ๋ก๊ทธ์์ ๋ง์คํนdba_alter_userโ ๋น๋ฐ๋ฒํธยท์์ฑ ๋ณ๊ฒฝdba_drop_userโ ์ฌ์ฉ์/์ญํ ์ญ์ (confirm=trueํ์)dba_grantโ ๊ถํ ๋ถ์ฌ/ํ์(revoke=true),WITH GRANT OPTION์ง์dba_create_databaseโ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ(OWNER/ENCODING ๋๋ CHARACTER SET)dba_drop_databaseโ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ญ์ (confirm=trueํ์)dba_set_parameterโ ์ค์ ๋ณ๊ฒฝ(postgresALTER SYSTEM+pg_reload_conf(), mysqlSET GLOBAL/SESSION)dba_terminate_sessionโ ์ธ์ ์ข ๋ฃ/์ฟผ๋ฆฌ ์ทจ์(pg_terminate_backend/pg_cancel_backend, mysqlKILL)dba_run_maintenanceโ ์ ์ง๋ณด์(postgres VACUUM/ANALYZE/REINDEX, mysql ANALYZE/OPTIMIZE)dba_executeโ ์์ ๊ถํ SQL ์คํ(์์ค์ผ์ดํ ํด์น,confirm=trueํ์, ๊ฐ์ฌ ๋ก๊ทธ์ ์๋ฌธ ๊ธฐ๋ก)describe_db_schemaโ ์ฐ๊ฒฐ๋ ํ๋กํ์ผ DB์ ๋ผ์ด๋ธ ์คํค๋ง(information_schema)๋ฅผ ์กฐํํด ์นดํ๋ก๊ทธ์ ์๋ ํ ์ด๋ธ๋ SQL ์์ฑ ๊ทผ๊ฑฐ๋ก ์ ๊ณต. ์นดํ๋ก๊ทธ ์ฐ์ : ๋ฑ๋ก๋ ํ ์ด๋ธ/์ปฌ๋ผ์ ๋ ผ๋ฆฌ๋ช ยท์ค๋ช ์ ํจ๊ป ๋ถ์ด๊ณin_catalogํ๋๊ทธ๋ก ๊ตฌ๋ถ. ์ฝ๊ธฐ ์ ์ฉยท๋น์ ์ฅ. ๋ผ์ด๋ธ ์ ์ฉ ํ ์ด๋ธ์ ๊ฒ์ฆ๊น์ง ํต๊ณผ์ํค๋ ค๋ฉดapply_metadata_sync๋ก ๋ฐ์run_metadata_syncโ ์์ฒ DB์ ๋ฌผ๋ฆฌ ๋ชจ๋ธ(์คํค๋งยทํ ์ด๋ธยท๋ทฐยท์ปฌ๋ผยทPK/FK/Unique/Checkยท์ธ๋ฑ์คยท์ฝ๋ฉํธยทํ์์ถ์ )์ ๋ฒ์ ์ค๋ ์์ผ๋ก ์์งํ๊ณ ์ด์ ์ค๋ ์ ๋๋น ๋ณ๊ฒฝ๋ถ์ ๋ฐํ. ๊ธฐ๋ณธ ์ฆ๋ถ(์คํค๋ง ํด์ ๋์ผ ์ ์คํต). ์ญ์ ๋ ์ฆ์ ๋ฐ์ํ์ง ์๊ณ ํ๊ธฐ ํ๋ณด๋ก ํ์. ๋ฌผ๋ฆฌ ์ ๋ณด๋ง ์์งํ๋ฉฐ ์ ๋ฌด ์๋ฏธ(๋ ผ๋ฆฌ๋ช ยท์งํ)๋ ์ด์ ์นดํ๋ก๊ทธ์ ์ฐ์ง ์์apply_metadata_syncโ (๊ด๋ฆฌ์) ์์ฒ์ ์ต์ ์ค๋ ์์ ์นดํ๋ก๊ทธ์ ์๋ ๋ฐ์: ๋ฌผ๋ฆฌ ๋ชจ๋ธ(์ปฌ๋ผยทํ์ ยทNULLยทPK/FKยทFK ๊ด๊ณ)์ meta_physical_models.json/topology_relations.json์ ๋ณํฉ(๋ฐฑ์ )ํ๊ณ ํซ๋ฆฌ๋ก๋. ๋ฌผ๋ฆฌ ์ฌ์ค์ ์๋ ๋ฐ์ํ๋ ๊ธฐ์กด ์ค๋ช (์ ๋ฌด ์๋ฏธ)์ ๋ณด์กด, ์ญ์ ๋ถ์prune=true๊ฐ ์๋๋ฉด ํ๊ธฐ ํ๋ณด๋ก๋ง ํ์. ์ค์ผ์ค๋ฌ-sync-apply๋ก ๋งค ์ฑํฌ ์ ์๋ ์คํ ๊ฐ๋ฅlist_profile_catalogsโ ๋ฑ๋ก๋ DB ํ๋กํ์ผ๋ณ ์นดํ๋ก๊ทธ ์ํฌ์คํ์ด์ค(<data>/profiles/<profile>/) ์ ๋ฌดยทํ ์ด๋ธ/๊ด๊ณ ์ยท๊ตฌ์ถ ์๊ฐ ๋ชฉ๋ก. ํ๋กํ์ผ๋ง๋ค ๋ ๋ฆฝ ๋ฉํ๋ฐ์ดํฐ JSON์ ์กฐํยท๊ด๋ฆฌget_profile_catalogโ ํน์ ํ๋กํ์ผ ์ํฌ์คํ์ด์ค์ ์นดํ๋ก๊ทธ ์์ฝยท๋ฐ์ดํฐ์ ์ธ๋ฒคํ ๋ฆฌยทํฌ์ค ์กฐํbuild_profile_catalogโ (๊ด๋ฆฌ์) ํ๋กํ์ผ์ ๋ผ์ด๋ธ ์คํค๋ง๋ก ์ํฌ์คํ์ด์ค ๊ตฌ์ถ/๊ฐฑ์ (๋ฌผ๋ฆฌ ๋ชจ๋ธ์ ํ๋กํ์ผ ๋๋ ํฐ๋ฆฌ์ ๊ธฐ๋ก, ๊ธฐ์กด ์ค๋ช ๋ณด์กดยท์ญ์ ๋ ํ๊ธฐ ํ๋ณด)get_profile_dataset/put_profile_datasetโ ํ๋กํ์ผ ์ํฌ์คํ์ด์ค์ ๊ฐ๋ณ ๋ฉํ๋ฐ์ดํฐ JSON(overridesยทglossaryยทphysical_models ๋ฑ) ์กฐํ / (๊ด๋ฆฌ์) ๊ฒ์ฆยท๋ฐฑ์ ยท๋กค๋ฐฑ๊ณผ ํจ๊ป ๊ด๋ฆฌbuild_all_profile_catalogsโ (๊ด๋ฆฌ์) ๋ฑ๋ก๋ ๋ชจ๋ (๋๋ ์ ํ) ํ๋กํ์ผ์ ์ํฌ์คํ์ด์ค๋ฅผ ๋ผ์ด๋ธ DB์์ ์ผ๊ด ๊ตฌ์ถ/๊ฐฑ์ . ํ๋กํ์ผ๋ณ ๊ถํ ํ์ธยท์คํจ๋ ๊ฐ๋ณ ๋ณด๊ณ (๋ฐฐ์น ์ค๋จ ์์). ๋ค์ DB ์จ๋ณด๋ฉ์ฉimport_openmetadata_to_profileโ (๊ด๋ฆฌ์) OpenMetadata์ ํ๋ ์ด์ ๋ฉํ๋ฐ์ดํฐ(๋ ผ๋ฆฌ๋ช ยท์ค๋ช ยทPIIยท์ฉ์ด์ง)๋ฅผ ํน์ ํ๋กํ์ผ ์ํฌ์คํ์ด์ค๋ก import(์ ์ญ ์นดํ๋ก๊ทธ ์๋). ๊ฐ DB์ ์ ๋ฌด ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ทธ DB ์ํฌ์คํ์ด์ค์ ์๊ธ, ๋น ํ๋๋งยท๊ธฐ์กด๊ฐ ๋ณด์กด,apply=false๋ฏธ๋ฆฌ๋ณด๊ธฐget_active_catalog/set_active_catalogโ ํ์ฌ NL2SQL์ด ์ฐ๋ ์นดํ๋ก๊ทธ(๊ธฐ๋ณธ-datavs ํซ์ค์๋ ํ๋กํ์ผ ์ํฌ์คํ์ด์ค) ์กฐํ / (๊ด๋ฆฌ์) ๋ฌด์ฌ๊ธฐ๋ ์ ํ. DB ํ๋กํ์ผยท๊ฐ์ฌยท์ํฌ์คํ์ด์ค๋ ์ด์ ๋๋ ํฐ๋ฆฌ์ ๊ณ ์ (์ ํ ์ํฅ ์์), ๋จ๋ ๋ชจ๋ ์ ์ฉยท์ฌ๊ธฐ๋ ์-data๋ก ๋ณต๊ทget_sync_statusโ ์์ฒ๋ณ ์ ์ฅ๋ ์ค๋ ์ ๋ชฉ๋ก(์ต์ ์, ์์ง์๊ฐยท์คํค๋งํด์ยท๊ฐ์ฒด์)diff_metadata_snapshotsโ ๋ ์ค๋ ์ ๊ฐ ๋ณ๊ฒฝ๋ถ(ํ ์ด๋ธ/์ปฌ๋ผ ์ถ๊ฐยท์ญ์ , ํ์ /Null/ํค/์ฝ๋ฉํธ/์ธ๋ฑ์ค/๋ทฐSQL ๋ณ๊ฒฝ, ๊ฐ๊ฐ ์ฌ๊ฐ๋ยท์ฒ๋ฆฌ๋ฐฉ์นจ) ๊ณ์ฐprofile_metadata_assetsโ ์ปฌ๋ผ ํต๊ณ(ํ์ยทNull๋น์จยทdistinctยทmin/maxยท์์๊ฐยทํฌ๋งทํจํด)๋ฅผ ๋น์ฉ ์ ์ด(๋ชจ๋๋ณ ์ํ: fast 2k / standard 100k / deep ์ ์ฒด)ยท๊ฐ์ธ์ ๋ณด ๋ณดํธํ(๋ฏผ๊ฐ ์ปฌ๋ผ์ ์๋ณธ๊ฐยทmin/maxยท์์๊ฐ ๋ฏธ์ ์ฅ, ๊ธธ์ดยทํจํดยท๊ฑด์๋ง)์ผ๋ก ๊ณ์ฐ. ๊ฒฐ๊ณผ๋ ๊ฒํ ํ๋ณด์ด๋ฉฐ ์ด์ ์นดํ๋ก๊ทธ(column_stats)์ ์๋ ๋ฐ์ํ์ง ์์record_feedbackโ ์ง๋ฌธ/๋ถ์/ํ๋ณด/SQL/๊ฒ์ฆ์ค๋ฅ/์ฑํ์ฌ๋ถ/์คํ์๊ฐ์ ์๋ฒ๊ฐ ๋ถ์ฌํ actor/session/dataset ๋ฒ์์ ํจ๊ปpending/untrusted๊ฒํ ํ์ ์ ์ฅ; ์น์ธ ์ ์๋ ๊ฒ์ยทํ๋กฌํํธยทํ์ต์ ์ฌ์ฉํ์ง ์์review_feedbackโ ๊ด๋ฆฌ์ ์ ์ฉ ํผ๋๋ฐฑ ๊ฒํ ํ ์กฐํ ๋ฐ approve/reject; ์น์ธ๋ ๋ ์ฝ๋๋ง trusted ์ํ๋ก few-shotยท๊ฒ์ ๋ถ์คํธยทํ์ต ๋ฃฐ์ ์ฌ์ฉlist_datasets/get_datasetโ ์๋ฒ๊ฐ ์ฐธ์กฐํ๋ ๋ชจ๋ JSON ๋ฐ์ดํฐ์ ์ ๋ผ์ด๋ธ ๋ ์ง์คํธ๋ฆฌ: ์ฉ๋, ์คํค๋ง, ์ฌ์ฉ ๋๊ตฌ, ํ์/ํธ์ง๊ฐ๋ฅ ์ฌ๋ถ, ํ์ฌ ์ํ(์กด์ฌยทํฌ๊ธฐยท๋ก๋ ๊ฑด์ยท๋ก๋ ์ด์)์ ๋ด์ฉ ์ํput_datasetโ ๋ฐ์ดํฐ์ ๊ต์ฒด: JSON ํํ ๊ฒ์ฆ โ ๊ธฐ์กด ํ์ผ ๋ฐฑ์ (backups/) โ ์ฐ๊ธฐ โ ์นดํ๋ก๊ทธ ์ฌ์ปดํ์ผ โ ํซ์ค์(์ฌ๊ธฐ๋ ๋ถํ์). ์ปดํ์ผ ์คํจ๋ ์ ๊ท ์ค๋ฅ ๋ฐ์ ์ ์๋ ๋กค๋ฐฑ(force๋ก ๊ฐ์ ์ ์ฉ ๊ฐ๋ฅ)remove_datasetโ ์ ํ ๋ฐ์ดํฐ์ ์ ๊ฑฐ(๋ฐฑ์ ํ) + ํซ์ค์. ํ์(physical_models,logical_models)ยท์์คํ ๊ด๋ฆฌ(feedback,audit) ๋์์ ๊ฑฐ๋ถreload_catalogโ ๋์คํฌ ํ์ผ์ ์ง์ ์์ ํ ๊ฒฝ์ฐ(๋ณผ๋ฅจ ๋ง์ดํธ ๋ฑ) ์ฌ์ปดํ์ผ + ํซ์ค์get_catalog_healthโ ๋ฉํ ์ปดํ์ผ ๊ฒ์ฆ ๊ฒฐ๊ณผ(์ค๋ฅ/๊ฒฝ๊ณ ), ์ปค๋ฒ๋ฆฌ์ง ๊ฐญ, PII ๋ชฉ๋กget_metadata_qualityโ ํ ์ด๋ธ๋ณ ๋ฉํ๋ฐ์ดํฐ ํ์ง ์ ์(์์ ์ฑยท์ผ๊ด์ฑยท๊ด๊ณ์ฑยทํ๋กํ์ผ๋งยท์งํ์ฐ๊ฒฐยท์ฌ์ฉ์ฑยท๋ณด์์ฑ) 0โ100 + ๋ฑ๊ธ AโE, ์คํค๋ง/๋๋ฉ์ธ ์ง๊ณ, ๊ฐ์ ๋์.gate=true๋ฉด ๋ฆด๋ฆฌ์ค ์ฐจ๋จ ์กฐ๊ฑด(๋ก๋ ์ค๋ฅยท์งํ/์ธ์ฆ์กฐ์ธ ์์ยทPII ๋ฏธ๋ถ๋ฅยทํ์ง ํํ ๋ฏธ๋ฌ) ํ๊ฐ๋ก ์ ํsuggest_semantic_metadataโ ๋ ผ๋ฆฌ๋ช ยท์๋ฏธํ์ ยท์ค๋ช ์ด ์๋ ์ปฌ๋ผ์ ๋ํด ๊ท์น ๊ธฐ๋ฐ(์ฉ์ด์งยท๋์ผ์ปฌ๋ผ ์ฌ์ฌ์ฉยท์ฝ์ด ํ์ฅยท์ด๋ฆ/ํ์ ํจํด, ์คํ๋ผ์ธ)์ผ๋ก ๊ฒํ ํ๋ณด๋ฅผ ๊ทผ๊ฑฐยท์ ๋ขฐ๋์ ํจ๊ป ์์ฑ. ๊ณ ์ ๋ขฐ ํญ๋ชฉ์ overrides.json columns[] ์ค๋ํซ์ผ๋ก ๋ฐํ. ์ด์ ์นดํ๋ก๊ทธ์ ์๋ ๋ฐ์ํ์ง ์์ผ๋ฉฐ LLM/๋ด๋น์๊ฐ ๋ค๋ฌ์ด ์น์ธsuggest_model_candidatesโ ๊ท์น ๊ธฐ๋ฐ ๋ชจ๋ธ ํ๋ณด ์์ฑ: ์ฝ๋์ฌ์ (์ ์นด๋๋๋ฆฌํฐ ์ฝ๋ ์ปฌ๋ผ์ ํ๋กํ์ผ top-value๋ก ์ค์ผ๋ ํค), ์งํ(AMOUNT/COUNT/RATIO/SCORE ์ปฌ๋ผโSUM/AVG ์ง๊ณ ์งํ), ๊ด๊ณ(์๋ณ์ ์ด๋ฆ+PK๋ช /ํ ์ด๋ธ๋ช ๋งค์นญ+ํ์ ํธํ์ผ๋ก FK ์ถ๋ก ). ๊ทผ๊ฑฐยท์ ๋ขฐ๋ ๋๋ฐ, ์ด์ ์นดํ๋ก๊ทธ ์๋ ๋ฏธ๋ฐ์analyze_impactโ ํ ์ด๋ธ/์ปฌ๋ผ ๋ณ๊ฒฝยทํ๊ธฐ ์ ๊ณ๋ณด/์ํฅ๋ ์ถ์ : ํด๋น ์์ฐ์ ์์กดํ๋ ์งํยท๊ด๊ณยท์ ํธ/๊ธ์ง ์กฐ์ธยท๊ณจ๋ ์ ยท์ค๋ฒ๋ผ์ด๋ยท์ฉ์ด์งยท1ํ ํ์ ํ ์ด๋ธ์ ์ญ์ถ์ ํ๊ณ impact_level(์งํ/์ ํธ์กฐ์ธ ์์กด ์ high) ์ฐ์ถ. ์นดํ๋ก๊ทธ ์ฝ๊ธฐ ์ ์ฉ ๋ถ์review_candidatesโ ์๋ฏธ๋ณด๊ฐยท๋ชจ๋ธ ํ๋ณด๋ฅผ ์ ์ฅ๋ ์น์ธ/๋ฐ๋ ค ๊ฒฐ์ ๊ณผ ์กฐ์ธํด ๊ฒํ ํ๋ก ์กฐํ(์ํ pending/approved/rejected ํํฐ). ๊ฐ ํญ๋ชฉ์ ์์ ์ id ๋ถ์ฌ. ์ฌ๋ ๊ฐ์ ๊ฒ์ดํธdecide_candidatesโ ํ๋ณด๋ฅผ id๋ก ์น์ธ/๋ฐ๋ ค. ๊ฒํ ์ยท์๊ฐยท๋ฉ๋ชจ์ ํจ๊ป ์์ ์ ์ฅ(<data>/reviews/decisions.json). ์นดํ๋ก๊ทธ ์๋ ๋ฏธ๋ฐ์get_metadata_digestโ ์นดํ๋ก๊ทธ ์ด์ ์ํ ์์ฝ ์ค๋ ์: ํ์ง ์ ์ยท๋ฆด๋ฆฌ์ค ๊ฒ์ดํธ, ๊ฒํ ํ ๋ฐฑ๋ก๊ทธ(๋๊ธฐ/์น์ธ/๋ฐ๋ ค), ๊ณจ๋ ์น๊ฒฉ ํ๋ณด ์, ์นดํ๋ก๊ทธ ๊ท๋ชจยท๋ก๋ ๊ฒฝ๊ณ + ํ ์ค ํค๋๋ผ์ธ. ์ผ์ผ ์ ๊ฒยท์๋ฆผ์ฉopenmetadata_statusโ ์ค์ ๋ OpenMetadata ์๋ฒ ์ฐ๊ฒฐยท์ธ์ฆยท๋ฒ์ ํ์ธimport_openmetadataโ OpenMetadata์ ํ๋ ์ด์ ๋ฉํ๋ฐ์ดํฐ(ํ ์ด๋ธ/์ปฌ๋ผ displayNameโ๋ ผ๋ฆฌ๋ช , ์ค๋ช , PII ํ๊ทธโpii/semantic_type, ์ฉ์ด์ง)๋ฅผ jamypg ๋น ํ๋์๋ง ํ๋ณด๋ก ๊ฐ์ ธ์ค๊ธฐ.apply=false๋ฏธ๋ฆฌ๋ณด๊ธฐ(๊ธฐ๋ณธ),apply=trueoverrides.json/glossary.json ๋ณํฉ+๋ฆฌ๋ก๋(๊ด๋ฆฌ์, ๋ฐฑ์ ยท์๊ธฐ๊ฐ ๋ณดํธ)export_to_openmetadataโ jamypg ์ปฌ๋ผ ์ค๋ช (๋ช ์์ ๋๋ ๋ ผ๋ฆฌ๋ช ์กฐํฉ)์ OpenMetadata์ ๋น ์ค๋ช ์ปฌ๋ผ์๋ง JSON-Patch๋ก push.dry_run=true๊ณํ๋ง(๊ธฐ๋ณธ),dry_run=false์ค์ ๋ฐ์(๊ด๋ฆฌ์)openmetadata_driftโ jamypg โ OpenMetadata ๋์กฐ(reconciliation) ๋ฆฌํฌํธ: ๋ ผ๋ฆฌ๋ช ยท์ค๋ช ยทPII๋ฅผjamypg_gap(import ํ๋ณด)ยทconflict(๊ฐ ๋ถ์ผ์น, ์ฌ๋ ๊ฒฐ์ )ยทext_gap(export ํ๋ณด)์ผ๋ก ๋ถ๋ฅ. ์ฝ๊ธฐ ์ ์ฉ ๊ฑฐ๋ฒ๋์ค ๋๊ตฌexport_lineage_to_openmetadataโ jamypg ๊ด๊ณ ๊ทธ๋ํ๋ฅผ OpenMetadata ํ ์ด๋ธ lineage ์ฃ์ง๋ก push(from=์ฐธ์กฐ/๋ถ๋ชจ, to=๊ธฐ์ค/์์). FK ๊ด๊ณํ lineage ๋งคํ(ETL ํ๋ฆ ์๋).dry_run=true๊ณํ(๊ธฐ๋ณธ)/false๋ฐ์(๊ด๋ฆฌ์), OM์ ์๋ ํ ์ด๋ธ ์ฃ์ง๋ skip ๋ณด๊ณget_approved_overridesโ ์น์ธ๋ ํ๋ณด๋ฅผ ๋ชฉ์ ํ์ผ๋ณ(overrides.json columns[], metrics.json, relations.json, ์ฝ๋์ฌ์ ) ์ ์ฉ ์ค๋ํซ์ผ๋ก ์ปดํ์ผapply_approved_candidatesโ ์ํด๋ฆญ ๋ฐ์: ์น์ธ-๋ฏธ๋ฐ์ ํ๋ณด๋ฅผ ๋ฐ์ดํฐ์ ํ์ผ 4์ข ์ ํ์ผ๋ณ ๋ฐฑ์ ํ ๋ณํฉํ๊ณ ์นดํ๋ก๊ทธ ํซ๋ฆฌ๋ก๋. ๋ฉฑ๋ฑ(applied_at ์คํฌํ+๋ด์ฉ ์ค๋ณต ์ ๊ฑฐ), ์ด์์ ์๊ธฐ ๊ฐ์ ๋ฎ์ด์ฐ์ง ์์. ๊ด๋ฆฌ์ ์ ์ฉrun_evaluationโ golden query set ํ๊ฐ(ํ ์ด๋ธ/์ปฌ๋ผ/์งํ/์กฐ์ธ/SQL ์ ํจ์ฑ ์ ํ๋, ํ๊ท ์๋ต์๊ฐ)learn_from_feedbackโ ๋ฐ๋ณต ์คํจ ํจํด์ learned rule๋ก ์น๊ฒฉ: ๋์ผ ๊ฒ์ฆ์ค๋ฅ ๋ฐ๋ณต(์๋ฐฉ ๊ฒฝ๊ณ ), ํ ์ด๋ธ ์ค์ ํ ๊ต์ (๊ฒ์ ํจ๋ํฐ), ์ปฌ๋ผ ๊ต์ (validate_sql ๊ฒฝ๊ณ ).learned_rules.json์ ์์ํ๋์ด ์ด์์๊ฐ ๊ฒํ /์์ ๊ฐ๋ฅsuggest_golden_from_feedbackโ ์น์ธยท์ฑ๊ณตยท์คํ๋ ํผ๋๋ฐฑ์ ๊ณจ๋ ์ ํ๋ณด๋ก ์ ์(์ง๋ฌธ/๊ธฐ๋ SQLยทํ ์ด๋ธยท์ปฌ๋ผ, ์ง๋ฌธ/SQL ์ ๊ทํ๋ก ๊ธฐ์กด ๊ณจ๋ ์ ์ค๋ณต ์ ์ธ). trust ๊ฒฝ๊ณ ์น์ธ๋ถ๋ง ๋์(fail-closed)promote_golden_queriesโ ์ ํ ํ๋ณด(feedback_id)๋ฅผgolden_queries.json์ ๋ฐฑ์ ํ ์ถ๊ฐํ๊ณ ์นดํ๋ก๊ทธ ๋ฆฌ๋ก๋. ์ด์ ํธ๋ํฝ์ผ๋ก ํ๊ฐ์ ์ ์ฑ์ฅ์ํค๋ ๋ช ์์ ๊ด๋ฆฌ์ ํ์. ๊ด๋ฆฌ์ ์ ์ฉ
run_sql_safely validates SQL and, when a DB profile is supplied, executes it
read-only against the target database (postgres/mysql/mariadb) with query
timeout, row limit, and audit logging โ drivers are always compiled in.
Without a profile it stays a dry-run guard returning bounded SQL. See
docs/db-connector.md. Start most questions with prepare_sql_context,
which runs the whole analyzeโskeleton pipeline in one call.
Web Admin Console & REST API
HTTP ๋ชจ๋๋ก ๊ธฐ๋ํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ ๊ด๋ฆฌ ํ๋ฉด๊ณผ Swagger ๋ฌธ์๊ฐ ํจ๊ป ์ ๊ณต๋ฉ๋๋ค.
๋ณ๊ฒฝ API ๋ณดํธ: -admin-token <๊ฐ> ํ๋๊ทธ(๋๋ JAMYPG_ADMIN_TOKEN ํ๊ฒฝ๋ณ์)๋ฅผ
์ค์ ํ๋ฉด PUT/DELETE/POST์ X-Admin-Token ํค๋๊ฐ ํ์ํฉ๋๋ค. ๋ฏธ์ค์ ์ ์ธ์ฆ
์์ด ํธ์ถ ๊ฐ๋ฅํ๋ฏ๋ก ๋ด๋ถ๋ง ์ธ ๋
ธ์ถ ์ ๋ฐ๋์ ์ค์ ํ์ธ์. ๋ชจ๋ ๋ณ๊ฒฝ์
audit/*.jsonl์ ๊ธฐ๋ก๋๊ณ , REST์ MCP ๋๊ตฌ(put_dataset ๋ฑ)๋ ๋์ผํ
๊ฒ์ฆยท๋ฐฑ์
ยท๋กค๋ฐฑ ์ฝ๋๋ฅผ ๊ณต์ ํฉ๋๋ค.
๋จ๋
HTTP ๋ชจ๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก loopback ์ฃผ์๋ง ํ์ฉํฉ๋๋ค. 0.0.0.0, ::,
์ธํฐํ์ด์ค IP ๋๋ hostname์ ๋ฐ์ธ๋ฉํ๋ ค๋ฉด ์ ๋ฉด ์ธ์ฆ์ ์ ๊ณตํ๋ -meta-db๋ฅผ
๊ตฌ์ฑํ๊ฑฐ๋ -public-mcp๋ก ๊ณต๊ฐ ๋
ธ์ถ์ ๋ช
์์ ์ผ๋ก ์น์ธํ๊ณ -admin-token๋
์ค์ ํด์ผ ํฉ๋๋ค.
-admin-token์ ๋ณ๊ฒฝยทDB ์คํ ๋๊ตฌ๋ฅผ ๋ณดํธํ์ง๋ง ๋ชจ๋ ์ฝ๊ธฐ ์ ์ฉ MCP ๋๊ตฌ์
๋ก๊ทธ์ธ์ ๊ฐ์ ํ์ง ์์ผ๋ฏ๋ก, ์ธํฐ๋ท ๋
ธ์ถ์๋ -meta-db ์ธ์ฆ์ ์ฌ์ฉํ์ธ์.
ํผ๋๋ฐฑ์ workspace๋ณ๋ก ๊ฒฉ๋ฆฌํ๋ ค๋ฉด -feedback-tenant ๋๋
JAMYPG_FEEDBACK_TENANT๋ฅผ ์๋ฒ๊ฐ ๊ด๋ฆฌํ๋ ๊ณ ์ ๊ฐ์ผ๋ก ์ค์ ํ์ธ์.
Authentication (optional, Postgres meta DB)
-meta-db <postgres DSN>(๋๋ JAMYPG_META_DB)๋ฅผ ์ง์ ํ๋ฉด ์ ๋ฉด ์ธ์ฆ์ด
ํ์ฑํ๋ฉ๋๋ค. ๋ฏธ์ง์ ์ ๊ธฐ์กด ๋จ๋
๋ชจ๋ ๊ทธ๋๋ก ๋์ํฉ๋๋ค(ํ์ ํธํ).
jamypg-mcp -transport http -addr 0.0.0.0:9797 \
-meta-db 'postgres://jamypg:pw@pg:5432/jamypg?sslmode=require' \
-bootstrap-admin 'admin:์ฒซ๊ด๋ฆฌ์๋น๋ฐ๋ฒํธ'
-
๋ก๊ทธ์ธ: ๋ก์ปฌ ๊ณ์ (bcrypt) + ์ธ์ ์ฟ ํค, ๋๋ Keycloak SSO(OIDC) (
-oidc-issuer/-oidc-client-id/-oidc-client-secret/-oidc-redirect-url) -
์ญํ :
admin(์ ๊ถ) /user. ๊ด๋ฆฌ์๋ ์ฌ์ฉ์ยท๋ฐ์ดํฐ์ ยท์ ์ฒด ํ๋กํ์ผยท ์ ์ฒด ํค ๊ด๋ฆฌ -
MCP ํค:
/mcp์ ๊ทผ์ฉjsk_...ํค๋ฅผ ๋ฐ๊ธยทํ์ ยทํ๊ธฐ(/admin/keys). ํด๋ผ์ด์ธํธ๋Authorization: Bearer jsk_...๋๋X-MCP-Key๋ก ์ ์ -
DB ํ๋กํ์ผ ๊ถํ: ์ฌ์ฉ์๋ณ ์์ +
use/managegrant +shared๊ณต๊ฐ. Postgres์ ์ ์ฅ๋์ด ์ฌ์ฉ์๋ง๋ค ์ ๊ทผ ๋ฒ์๊ฐ ๋ค๋ฆ -
์ฒซ ๊ธฐ๋ ์ ๋ถํธ์คํธ๋ฉ ๊ด๋ฆฌ์๋ฅผ ์์ฑ(๋น๋ฐ๋ฒํธ ๋ฏธ์ง์ ์ ๋ก๊ทธ์ 1ํ ์ถ๋ ฅ)
-
์๋ฒ ์ค์ ๊ด๋ฆฌ: ๋ง์คํฐ ํ ํฐยทํ์ฉ OriginยทKeycloak SSO๋ฅผ
/admin/settings์์ ๋ฉํ DB์ ์ ์ฅํ๊ณ ์ฌ๊ธฐ๋ ์์ด ์ฆ์ ์ ์ฉ(ํ๋๊ทธ/env๋ ๊ธฐ๋ณธ๊ฐ) -
๋ฐ์ดํฐ์ ๋ ๋ฉํ DB์์ ๊ด๋ฆฌ: ํธ์ง ๊ฐ๋ฅํ ์นดํ๋ก๊ทธ JSON 14์ข ์ ์ง์ค ์๋ณธ์ด Postgres(
jamypg_datasets)๊ฐ ๋์ด/adminยทMCP ๋๊ตฌ ํธ์ง์ด DB์ ์์ํ๋จ(๋ก๋ ์ ํ์ผ๋ก materializeํด ๊ธฐ์กด ๋ก๋ ์ฌ์ฌ์ฉ) -
MCP
list_db_profiles: LLM์ด ์ฌ์ฉ ๊ฐ๋ฅํ DB ํ๋กํ์ผ id๋ฅผ ๋ฐ๊ฒฌ
๋ฉํ DB ๋๋ผ์ด๋ฒ๋ ์์ Go(pgx)๋ผ CGO/์ธ๋ถ ํด๋ผ์ด์ธํธ๊ฐ ํ์ ์์ต๋๋ค. ์์ธ: docs/auth.md.
Operator-Managed Data Files (dataset dir)
Evaluation
go test ./... # golden set ํฌํจ ์ ์ฒด ํ
์คํธ (CI)
go run ./cmd/jamypg-eval -verbose # ํ๊ฐ๋ง ์คํ, ์ผ์ด์ค๋ณ ๋ฏธ์ค ์ถ๋ ฅ
go run ./cmd/jamypg-eval -data data/metadb -profile pg-meta
# ์คํ ๊ธฐ๋ฐ ํ๊ฐ (์ค์ DB์ COUNT ๊ฒ์ฆ)
go run ./cmd/jamypg-goldgen -n 80 # sql_datasets์์ golden set ์ฌ์์ฑ
# (๋๋ฉ์ธ x ๋์ด๋ ์ธตํ, ์นดํ๋ก๊ทธ ๊ฒ์ฆ ํต๊ณผ ์ผ์ด์ค๋ง,
# ๊ธฐ์กด ํ์ผ ์๋จ ์์์
์ผ์ด์ค๋ -keep ๊ฐ์๋งํผ ๋ณด์กด)
์ธก์ ํญ๋ชฉ: table_selection_acc, column_recall_avg, metric_lookup_acc,
join_path_acc, expected_sql_valid, avg_response_ms (+ -profile ์
execution_success_rate, row_sanity_rate).
data/metadb 8์ผ์ด์ค(3๊ฐ DB ์ค์ธก): table 1.0 / join 1.0 / sql 1.0 / ์คํ ์ฑ๊ณต๋ฅ 1.0.
OSS ๋ฐ์ดํฐ์
(sakila/northwind/wordpress) 17์ผ์ด์ค: 3๊ฐ ์์ง์์ ๋์ผ ์ ๋ต ๊ฒ์ฆ ํต๊ณผ.
Feedback Learning Loop
- ํด๋ผ์ด์ธํธ๊ฐ
record_feedback์ผ๋ก ์ง๋ฌธ/SQL/๊ฒ์ฆ์ค๋ฅ/๊ต์ ๋ณธ/์ฑํ์ฌ๋ถ๋ฅผ ๊ฒํ ํ์ ์ ์ฅ - ๊ด๋ฆฌ์๊ฐ
review_feedback์ผ๋ก ๋ด์ฉ๊ณผ ๋ฒ์๋ฅผ ํ์ธํด approve/reject - ์น์ธ๋ trusted ์ฑ๊ณตยท๊ต์ SQL๋ง ์ฆ์ few-shot ์์ ์ ๊ฒ์ ๋ถ์คํธ์ ๋ฐ์
learn_from_feedbackํธ์ถ(๋๋ ์ฃผ๊ธฐ ์คํ) ์ ์น์ธ๋ ํผ๋๋ฐฑ์ ๋ฐ๋ณต ํจํด์ ๋ฃฐ๋ก ์น๊ฒฉ:recurring_errorโ ๊ฐ์ ๊ฒ์ฆ ์ค๋ฅ๊ฐ Nํ ์ด์ โ ํด๋น ํ ์ด๋ธ/์ปฌ๋ผ ์ฌ์ฉ ์ ์๋ฐฉ ๊ฒฝ๊ณtable_correctionโ ๊ต์ ์์ ๋ฐ๋ณต์ ์ผ๋ก ๊ต์ฒด๋ ํ ์ด๋ธ โ ๊ฒ์ ์ ์ ํจ๋ํฐ + ๊ฒฝ๊ณcolumn_correctionโ ๋ฐ๋ณต ๊ต์ฒด๋ ์ปฌ๋ผ โ validate_sql์ด ๋์ฒด ์ปฌ๋ผ ํํธ ์ ์slow_query/recurring_exec_errorโ ์คํ ๊ฐ์ฌ ๋ก๊ทธ์์ ๋ฐ๋ณต ์ง์ฐยท์ค๋ฅ(PG-/MY-/TIMEOUT) ์น๊ฒฉ
- ๋ฃฐ์
learned_rules.json์ผ๋ก ์์ํ; ์๋ฒ ์ฌ๊ธฐ๋ ์ ์๋ ์ ์ฉ, ์ด์์๊ฐ ์ง์ ํธ์ง ๊ฐ๋ฅ
SQL Generation Flow
analyze_questionโ ๋ชจํธ์ฑ ํ์ธ (๊ธฐ๋ณธ๊ฐ ์ ์ฉ ์ ๊ฐ์ ํ์), ํจํดยทintent ์๊ทธ๋์ฒ ํ๋ณดsearch_schema(+find_filter_columns,resolve_time)get_metric_definitionโ ์ ๋ฌด ์งํ๋ ์ฌ์ expression๋ง ์ฌ์ฉget_schema_contextโ ์์ถ ์ปจํ ์คํธ๋ง LLM์ ์ ๋ฌget_join_pathsโ ON ์กฐ๊ฑด์ ๋ฐ๋์ ์ฌ๊ธฐ์ ์ทจ๋; ๊ฒฝ๋ก ์์/์ ์ ๋ขฐ ์ ๋๋ฌป๊ธฐ- ๋ณต์ก/๋ค์ค ํ
์ด๋ธ ์ง๋ฌธ์ด๋ฉด
build_sql_skeleton์ผ๋ก ๊ณจ๊ฒฉ ํ๋ณด ํ SLOT๋ง ์ฑ์; ๋จ์ ์ง๋ฌธ์ ์ง์ ์์ฑ (์ปจํ ์คํธ ๋ด ์๋ณ์๋ง, PII ๊ธ์ง, row bound(LIMIT) ํ์) validate_sql(expected_outputs,metrics์ ๋ฌ) โ fix_hints ๋ฐ์ ์ต๋ 2ํ ์ฌ์๋; ์คํจ SQL ์คํ ๊ธ์ง. ๋์ด๋ ๋์ ์ง๋ฌธ์ ํ๋ณด 2~3๊ฐ๋ฅผ ๋ง๋ค์ดrank_candidates๋ก ์ต์ ์ ์ ํexplain_sqlโ risk=high๋ฉด ๊ธฐ๊ฐ/limit ์กฐ๊ฑด ์ถ๊ฐ ํ ์ฌ์์ฑ (profile์ง์ ์ ์ค์ธก EXPLAIN)- ๊ตฌ์กฐํ JSON ์๋ต (sql, ์ฌ์ฉ ํ ์ด๋ธ/์ปฌ๋ผ, ์งํ, ์กฐ์ธ, ํํฐ, ๊ฐ์ , ์ฃผ์, ๊ฒ์ฆ๊ฒฐ๊ณผ, ์คํ๊ฐ๋ฅ์ฌ๋ถ)
record_feedback