An open API service indexing awesome lists of open source software.

https://github.com/hkjang/jamypg

MCP server for metadata-grounded NL2SQL over PostgreSQL, MySQL, and MariaDB
https://github.com/hkjang/jamypg

agent ai ai-agents mariadb mcp mysql nl2sql pg postgres text2sql

Last synced: 4 days ago
JSON representation

MCP server for metadata-grounded NL2SQL over PostgreSQL, MySQL, and MariaDB

Awesome Lists containing this project

README

          

# JAMYPG NL2SQL MCP


jamypg Logo

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](docs/README.md) โ€” ์•„ํ‚คํ…์ฒ˜, MCP ๋„๊ตฌ
๋ ˆํผ๋Ÿฐ์Šค(85์ข…), SQL ์ƒ์„ฑ ์›Œํฌํ”Œ๋กœ, ๊ฒ€์ฆ ๋ฃฐ ์นดํƒˆ๋กœ๊ทธ(33์ข…), ๋ฐ์ดํ„ฐ์…‹
๊ฐ€์ด๋“œ(18์ข…), REST API, DB ์ปค๋„ฅํ„ฐ, ์šด์˜/ํ‰๊ฐ€/๋ณด์•ˆ/๊ฐœ๋ฐœ์ž ๊ฐ€์ด๋“œ.

## Quick Start

| ๋ชฉ์  | ๋ช…๋ น |
| --- | --- |
| ๋กœ์ปฌ HTTP MCP + ๊ด€๋ฆฌ์ž UI | `go run ./cmd/jamypg-mcp -transport http -data ./data/metadb -addr 127.0.0.1:9797` |
| ๋กœ์ปฌ stdio MCP | `go run ./cmd/jamypg-mcp -transport stdio -data ./data/metadb` |
| ์ปจํ…Œ์ด๋„ˆ (๋ชจ๋“  DB ์‹คํ–‰ ๊ฐ€๋Šฅ) | `docker build -t jamypg-mcp:v0.58.0 .` |
| ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ DB 3์ข… ๊ธฐ๋™ | `docker compose -f deploy/test/docker-compose.yml up -d` |
| ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (pg+mysql+mariadb) | `go test -tags integration ./test/integration -v` |

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

| DB | ํ”„๋กœํŒŒ์ผ `type` | ๋“œ๋ผ์ด๋ฒ„ | read-only ์„ธ์…˜ ๊ฐ•์ œ |
| --- | --- | --- | --- |
| PostgreSQL | `postgres` (๊ธฐ๋ณธ) | `pgx/v5` (pure Go) | `default_transaction_read_only=on` |
| MySQL 8.x | `mysql` | `go-sql-driver/mysql` (pure Go) | `transaction_read_only=1` |
| MariaDB 10.x/11.x | `mariadb` | `go-sql-driver/mysql` (pure Go) | `tx_read_only=1` |

`connect_string`์€ `host:port/dbname` ์ถ•์•ฝํ˜•, `postgres://`/`mysql://` URL,
go-sql-driver DSN์„ ๋ชจ๋‘ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ SQL์˜ ๋ฐฉ์–ธ์€ ๋ฐ์ดํ„ฐ์…‹์˜
`databases.json`(`dbms`) ๋˜๋Š” `overrides.json`(`dialect`)์ด ๊ฒฐ์ •ํ•˜๋ฉฐ ๊ธฐ๋ณธ์€
postgres์ž…๋‹ˆ๋‹ค. ์ƒ์„ธ: [docs/db-connector.md](docs/db-connector.md).

## NL2SQL Recommended Flow

๋Œ€๋ถ€๋ถ„์˜ ์งˆ๋ฌธ์€ ๊ฐœ๋ณ„ ๋„๊ตฌ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ํ•˜์ง€ ๋ง๊ณ 
`prepare_sql_context`๋ถ€ํ„ฐ ํ˜ธ์ถœํ•˜์„ธ์š”.

1. `prepare_sql_context(question)` ํ˜ธ์ถœ
2. ์‘๋‹ต์ด `status: "needs_clarification"`์ด๋ฉด SQL์„ ๋งŒ๋“ค์ง€ ๋ง๊ณ 
`clarifications`์˜ ์งˆ๋ฌธ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋˜๋ฌป์Šต๋‹ˆ๋‹ค.
3. ๋‹ต์„ ๋ฐ›์€ ๋’ค `prepare_sql_context(question, clarifications={...})`๋กœ ๋‹ค์‹œ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
4. `status: "ready"`์ด๋ฉด `skeleton.skeleton_sql`์˜ `/* SLOT */`๋งŒ ์ฑ„์›Œ SQL์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.
5. `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:

```powershell
.\scripts\build.ps1
```

Linux/macOS shell:

```sh
sh ./scripts/build.sh
```

Artifacts:

```text
dist/jamypg-mcp-windows-amd64.exe
dist/jamypg-mcp-linux-amd64
dist/jamypg-mcp-linux-arm64
```

Single-platform builds:

```powershell
go build -o .\bin\jamypg-mcp.exe .\cmd\jamypg-mcp
```

```sh
go build -o ./bin/jamypg-mcp ./cmd/jamypg-mcp
```

## Docker Image

๋‹จ์ผ `Dockerfile`์ด ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์™„์ „ํ•œ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค (๊ณผ๊ฑฐ์˜
`Dockerfile.oracle`/Instant Client ์ ˆ์ฐจ๋Š” ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค):

```sh
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](docs/db-connector.md).

## Integration Test Environment (pg + mysql + mariadb)

jamypg์˜ **๋ฉ”ํƒ€ DB ์Šคํ‚ค๋งˆ ์ž์ฒด๋ฅผ text2sql ๋Œ€์ƒ**์œผ๋กœ ์„ธ ์—”์ง„์— ์ ์žฌํ•œ
ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

```sh
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:metapw@127.0.0.1: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`๋กœ
์žฌ์ƒ์„ฑ):

| ๋ฐ์ดํ„ฐ์…‹ | ์Šคํ‚ค๋งˆ | ์œ ๋ž˜ | ๊ณจ๋“ ์…‹ |
| --- | --- | --- | --- |
| `data/sakila` | sakila (9 tables: film/actor/customer/rental/payment...) | MySQL ๊ณต์‹ ์ƒ˜ํ”Œ DB (DVD ๋ Œํƒˆ) | 6 (์ •๋‹ต ๊ฒ€์ฆ ํฌํ•จ) |
| `data/northwind` | northwind (8 tables: products/orders/customers...) | ๊ณ ์ „ ์ฃผ๋ฌธ๊ด€๋ฆฌ ์ƒ˜ํ”Œ | 6 (์ •๋‹ต ๊ฒ€์ฆ ํฌํ•จ) |
| `data/wordpress` | wordpress (8 tables: wp_posts/wp_comments/wp_terms...) | WordPress CMS ํ•ต์‹ฌ ํ…Œ์ด๋ธ” | 5 (์ •๋‹ต ๊ฒ€์ฆ ํฌํ•จ) |

์„ธ ์Šคํ‚ค๋งˆ ๋ชจ๋‘ PostgreSQL(์Šคํ‚ค๋งˆ)ยทMySQL/MariaDB(๋™๋ช… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)์— ๋™์ผํ•˜๊ฒŒ
์ ์žฌ๋˜์–ด `sakila.film` ๊ฐ™์€ ์Šคํ‚ค๋งˆ ํ•œ์ • SQL์ด ์„ธ ์—”์ง„์—์„œ ๊ทธ๋Œ€๋กœ ์‹คํ–‰๋˜๊ณ ,
๊ณจ๋“ ์…‹์˜ ๊ธฐ๋Œ€ ์ •๋‹ต(์˜ˆ: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์˜ํ™” ์ˆ˜ 1์œ„)์ด ์„ธ ์—”์ง„์—์„œ ์ผ์น˜ํ•˜๋Š”์ง€๊นŒ์ง€
ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค:

```sh
go run ./cmd/jamypg-mcp -data data/sakila -addr 127.0.0.1:9797 # ํ”„๋กœํŒŒ์ผ: pg-sakila / mysql-sakila / mariadb-sakila
```

## Run With stdio

Windows:

```powershell
.\dist\jamypg-mcp-windows-amd64.exe -transport stdio -data .\data\metadb
```

Linux:

```sh
chmod +x ./dist/jamypg-mcp-linux-amd64
./dist/jamypg-mcp-linux-amd64 -transport stdio -data ./data/metadb
```

Example MCP client config for Windows:

```json
{
"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:

```json
{
"mcpServers": {
"jamypg": {
"command": "/opt/jamypg/dist/jamypg-mcp-linux-amd64",
"args": ["-transport", "stdio", "-data", "/opt/jamypg/data/metadb"]
}
}
}
```

stdio smoke test:

```powershell
$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
```

```sh
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

```powershell
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -addr 127.0.0.1:9797
```

MCP endpoint:

```text
http://127.0.0.1:9797/mcp
```

Health check:

```powershell
Invoke-RestMethod http://127.0.0.1:9797/healthz
```

## MCP Transport

This implements the MCP Streamable HTTP transport:

- `POST /mcp` accepts one JSON-RPC MCP message.
- `GET /mcp` opens a Server-Sent Events stream for server-to-client messages.
- `DELETE /mcp` closes a stateful session.
- `Mcp-Session-Id` is issued after `initialize` but never required: clients
that do not echo the header (qwen-code, opencode, ...) are served normally
(lenient session policy).
- `MCP-Protocol-Version: 2025-06-18` is accepted on subsequent requests.
- Origin validation allows empty origins, `localhost`, `127.0.0.1`, and `::1`.

For stateless local testing:

```powershell
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -stateless
```

To return POST responses as SSE:

```powershell
go run ./cmd/jamypg-mcp -transport http -data .\data\metadb -sse-post
```

## Curl Smoke Test

Initialize:

```powershell
$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:

```powershell
$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:

```powershell
$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**(postgres `EXPLAIN (FORMAT JSON)`, mysql/mariadb `EXPLAIN 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 INDEX` DDL๊ณผ ๋Œ€ํ‘œ ์ฟผ๋ฆฌ ํฌํ•จ. ์ฝ๊ธฐ ์ „์šฉยท๊ถŒ๊ณ ์šฉ(์ž๋™ ์ƒ์„ฑํ•˜์ง€ ์•Š์œผ๋ฉฐ 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, mysql `CREATE 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` โ€” ์„ค์ • ๋ณ€๊ฒฝ(postgres `ALTER SYSTEM`+`pg_reload_conf()`, mysql `SET GLOBAL/SESSION`)
- `dba_terminate_session` โ€” ์„ธ์…˜ ์ข…๋ฃŒ/์ฟผ๋ฆฌ ์ทจ์†Œ(`pg_terminate_backend`/`pg_cancel_backend`, mysql `KILL`)
- `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 ํ”„๋กœํŒŒ์ผ๋ณ„ **์นดํƒˆ๋กœ๊ทธ ์›Œํฌ์ŠคํŽ˜์ด์Šค**(`/profiles//`) ์œ ๋ฌดยทํ…Œ์ด๋ธ”/๊ด€๊ณ„ ์ˆ˜ยท๊ตฌ์ถ• ์‹œ๊ฐ ๋ชฉ๋ก. ํ”„๋กœํŒŒ์ผ๋งˆ๋‹ค ๋…๋ฆฝ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ 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์ด ์“ฐ๋Š” ์นดํƒˆ๋กœ๊ทธ(๊ธฐ๋ณธ `-data` vs ํ•ซ์Šค์™‘๋œ ํ”„๋กœํŒŒ์ผ ์›Œํฌ์ŠคํŽ˜์ด์Šค) ์กฐํšŒ / **(๊ด€๋ฆฌ์ž)** **๋ฌด์žฌ๊ธฐ๋™ ์ „ํ™˜**. 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๋กœ **์Šน์ธ/๋ฐ˜๋ ค**. ๊ฒ€ํ† ์žยท์‹œ๊ฐยท๋ฉ”๋ชจ์™€ ํ•จ๊ป˜ ์˜์† ์ €์žฅ(`/reviews/decisions.json`). ์นดํƒˆ๋กœ๊ทธ ์ž๋™ ๋ฏธ๋ฐ˜์˜
- `get_metadata_digest` โ€” ์นดํƒˆ๋กœ๊ทธ ์šด์˜ ์ƒํƒœ **์š”์•ฝ ์Šค๋ƒ…์ˆ**: ํ’ˆ์งˆ ์ ์ˆ˜ยท๋ฆด๋ฆฌ์Šค ๊ฒŒ์ดํŠธ, ๊ฒ€ํ†  ํ ๋ฐฑ๋กœ๊ทธ(๋Œ€๊ธฐ/์Šน์ธ/๋ฐ˜๋ ค), ๊ณจ๋“  ์Šน๊ฒฉ ํ›„๋ณด ์ˆ˜, ์นดํƒˆ๋กœ๊ทธ ๊ทœ๋ชจยท๋กœ๋“œ ๊ฒฝ๊ณ  + ํ•œ ์ค„ ํ—ค๋“œ๋ผ์ธ. ์ผ์ผ ์ ๊ฒ€ยท์•Œ๋ฆผ์šฉ
- `openmetadata_status` โ€” ์„ค์ •๋œ OpenMetadata ์„œ๋ฒ„ ์—ฐ๊ฒฐยท์ธ์ฆยท๋ฒ„์ „ ํ™•์ธ
- `import_openmetadata` โ€” OpenMetadata์˜ ํ๋ ˆ์ด์…˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ displayNameโ†’๋…ผ๋ฆฌ๋ช…, ์„ค๋ช…, PII ํƒœ๊ทธโ†’pii/semantic_type, ์šฉ์–ด์ง‘)๋ฅผ jamypg **๋นˆ ํ•„๋“œ์—๋งŒ** ํ›„๋ณด๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ. `apply=false` ๋ฏธ๋ฆฌ๋ณด๊ธฐ(๊ธฐ๋ณธ), `apply=true` overrides.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 ๋ฌธ์„œ๊ฐ€ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

| ๊ฒฝ๋กœ | ๋‚ด์šฉ |
| --- | --- |
| `/admin` | **๋ฐ์ดํ„ฐ์…‹ ๊ด€๋ฆฌ ์ฝ˜์†”** โ€” 18๊ฐœ ๋ฐ์ดํ„ฐ์…‹์˜ ์šฉ๋„ยท์Šคํ‚ค๋งˆยท์ƒํƒœ ํ™•์ธ, ๋‚ด์šฉ ํŽธ์ง‘ยท์ ์šฉ(๋ฐฑ์—…+๊ฒ€์ฆ+ํ•ซ์Šค์™‘), ์ œ๊ฑฐ, ๋ฐฑ์—…/๋ณต์›, ์นดํƒˆ๋กœ๊ทธ ๋ฆฌ๋กœ๋“œ. ๋‹จ๊ณ„๋ณ„ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ๊ฐ€ ํ™”๋ฉด์— ๋‚ด์žฅ |
| `/admin/editor` | **ํ…Œ์ด๋ธ” ํŽธ์ง‘๊ธฐ** โ€” ๋ฐ์ดํ„ฐ์…‹์„ ํ‘œ(๊ทธ๋ฆฌ๋“œ)๋กœ ๋ Œ๋”๋งํ•ด JSON ์—†์ด ํŽธ์ง‘: ์…€ ํด๋ฆญ ์ธ๋ผ์ธ ์ˆ˜์ •(ํƒ€์ž… ์ž๋™ ๋ณด์กด), ํ–‰ ์ถ”๊ฐ€/๋ณต์ œ/์‚ญ์ œ, **์ปฌ๋Ÿผ ์ถ”๊ฐ€/์ด๋ฆ„๋ณ€๊ฒฝ/์‚ญ์ œ**, ๊ฒ€์ƒ‰ยทํŽ˜์ด์ง€๋„ค์ด์…˜. ์ €์žฅ ์‹œ ๋™์ผํ•œ ๋ฐฑ์—…ยท๊ฒ€์ฆยทํ•ซ์Šค์™‘ยท๋กค๋ฐฑ ์ ์šฉ |
| `/admin/db` | **DB ์—ฐ๊ฒฐ ๊ด€๋ฆฌยท์ฟผ๋ฆฌ ์‹คํ–‰** โ€” postgres/mysql/mariadb ํ”„๋กœํŒŒ์ผ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ/์ ‘์† ํ…Œ์ŠคํŠธ, Read-Only ์ฟผ๋ฆฌ ์ฝ˜์†”(๊ฒ€์ฆโ†’๋ฏธ๋ฆฌ๋ณด๊ธฐโ†’์‹คํ–‰โ†’์ทจ์†Œ), ์‹คํ–‰ ์ด๋ ฅยท๋ฉ”ํŠธ๋ฆญ ([docs/db-connector.md](docs/db-connector.md)) |
| `/admin/dba` | **DBA ์ฝ”ํŒŒ์ผ๋Ÿฟ** โ€” ์ฝ๊ธฐ ์ „์šฉ DBA ์ง„๋‹จ ๋Œ€์‹œ๋ณด๋“œ: ํ—ฌ์Šค ์ ๊ฒ€, ์ธ๋ฑ์Šค ์–ด๋“œ๋ฐ”์ด์ €(CREATE INDEX ํ›„๋ณด), ์›Œํฌ๋กœ๋“œ ๋ฆฌํฌํŠธ, SQL ์•ˆํ‹ฐํŒจํ„ด ๋ฆฐํŠธ, SQL ์ž์—ฐ์–ด ์„ค๋ช…์„ ํƒญ UI๋กœ ์ œ๊ณต(์ž๋™ ์‹คํ–‰ยท๋ณ€๊ฒฝ ์—†์Œ, ๊ถŒ๊ณ ์šฉ) |
| `/admin/dba-console` | **DBA ๊ด€๋ฆฌ ์ฝ˜์†”** (`dba`/`admin` ์—ญํ•  ์ „์šฉ) โ€” ๊ถŒํ•œ ์žˆ๋Š” ์“ฐ๊ธฐ ์„ธ์…˜์œผ๋กœ ์‚ฌ์šฉ์žยท์—ญํ• , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๊ถŒํ•œ(GRANT/REVOKE), ์„œ๋ฒ„ ์„ค์ •, ์„ธ์…˜(์ทจ์†Œ/์ข…๋ฃŒ), ์œ ์ง€๋ณด์ˆ˜(VACUUM/ANALYZE/REINDEX), ์ž„์˜ ๊ถŒํ•œ SQL์„ ํƒญ UI๋กœ ๊ด€๋ฆฌ. ํ”„๋กœํŒŒ์ผ์˜ `dba` ์ž๊ฒฉ์ฆ๋ช… ํ•„์š”, ๋ชจ๋“  ๋ณ€๊ฒฝ ๊ฐ์‚ฌ ๋กœ๊ทธ ๊ธฐ๋ก |
| `/auth/login` ยท `/admin/users` ยท `/admin/keys` | **์ธ์ฆยท์‚ฌ์šฉ์žยทMCP ํ‚ค** (๋ฉ”ํƒ€ DB ํ™œ์„ฑ ์‹œ) โ€” ๋กœ์ปฌ/Keycloak SSO ๋กœ๊ทธ์ธ, ์‚ฌ์šฉ์žยท์—ญํ•  ๊ด€๋ฆฌ(admin), MCP ํ‚ค ๋ฐœ๊ธ‰ยทํšŒ์ „ยทํ๊ธฐ, ํ”„๋กœํŒŒ์ผ๋ณ„ ๊ถŒํ•œ(grant). ์ƒ์„ธ: [docs/auth.md](docs/auth.md) |
| `/docs` | **Swagger UI** โ€” REST API ๋ฌธ์„œ + Try it out (์˜คํ”„๋ผ์ธ ๋™์ž‘, ์ž์‚ฐ ์ž„๋ฒ ๋“œ) |
| `/openapi.json` | OpenAPI 3.0 ์ŠคํŽ™ |
| `/api/*` | REST API: `GET /api/datasets`, `GET/PUT/DELETE /api/datasets/{name}`, `GET .../content`, `GET .../backups`, `POST .../restore`, `POST /api/reload`, `GET /api/health` |

๋ณ€๊ฒฝ 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 `(๋˜๋Š” `JAMYPG_META_DB`)๋ฅผ ์ง€์ •ํ•˜๋ฉด ์ „๋ฉด ์ธ์ฆ์ด
ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ๋ฏธ์ง€์ • ์‹œ ๊ธฐ์กด ๋‹จ๋… ๋ชจ๋“œ ๊ทธ๋Œ€๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค(ํ•˜์œ„ ํ˜ธํ™˜).

```sh
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`/`manage` grant + `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](docs/auth.md).

## Operator-Managed Data Files (dataset dir)

| ํŒŒ์ผ | ์šฉ๋„ |
| --- | --- |
| `glossary.json` | ์—…๋ฌด ์šฉ์–ด/๋™์˜์–ด ์‚ฌ์ „ (๊ฒ€์ƒ‰ยท์งˆ๋ฌธ๋ถ„ํ•ดยทSQL์ƒ์„ฑยท๊ฒ€์ฆ ๊ณต์šฉ) |
| `metrics.json` | ์ง€ํ‘œ ์‚ฌ์ „: expression, ์ง‘๊ณ„, grain, ํ•„์ˆ˜ ํ•„ํ„ฐ, ์˜ˆ์‹œ SQL |
| `overrides.json` | ์šด์˜์ž ๋ณด์ •: ์„ค๋ช…/๋„๋ฉ”์ธ/grain, ์ปฌ๋Ÿผ ๋™์˜์–ดยท์ƒ˜ํ”Œ๊ฐ’, PII ์ง€์ •, ๊ธˆ์ง€/๊ถŒ์žฅ ์กฐ์ธ, ๊ตฌ์กฐ ๊ฒ€์ฆ ๊ธฐ๋ณธ ํ•„ํ„ฐ(`enforcement: warn|error`), dialect(postgres/mysql/mariadb) |
| `databases.json` | ๋Œ€์ƒ DB ์ •๋ณด โ€” `dbms`(POSTGRES/MYSQL/MARIADB)๊ฐ€ ์ƒ์„ฑ SQL ๋ฐฉ์–ธ ๊ฒฐ์ • |
| `db_profiles.json` | ์‹คํ–‰์šฉ DB ์ ‘์† ํ”„๋กœํŒŒ์ผ (type/connect_string/password_ref/pool/policy) |
| `column_stats.json` | ์ปฌ๋Ÿผ ํ”„๋กœํŒŒ์ผ ํ†ต๊ณ„ (์„ ํƒ; row count, null ๋น„์œจ, top values, ์ตœ์‹ ์„ฑ) |
| `patterns.json` | ๋‹ค๋‹จ๊ณ„ SQL ํŒจํ„ด ์‚ฌ์ „ (2๋‹จ ์ง‘๊ณ„, ๊ทธ๋ฃน๋ณ„ top-N, ์ „์›”/์ „๋…„ ๋Œ€๋น„, ๋น„์œจ, ๋ถ„ํฌ) โ€” ๋ฏธ์กด์žฌ ์‹œ ๋‚ด์žฅ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ (๋ฐฉ์–ธ์— ๋งž๊ฒŒ ์ž๋™ ์น˜ํ™˜) |
| `golden_queries.json` | ํ‰๊ฐ€์šฉ golden query set โ€” ์ˆ˜์ž‘์—… ์ผ€์ด์Šค + `jamypg-goldgen` ์ž๋™ ์„ ๋ณ„ (CI์—์„œ `go test ./...`๋กœ ์ž๋™ ์‹คํ–‰) |
| `learned_rules.json` | `learn_from_feedback`๊ฐ€ ์Šน๊ฒฉํ•œ ํ•™์Šต ๋ฃฐ (์šด์˜์ž ๊ฒ€ํ† /์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ) |
| `feedback/*.jsonl` | record_feedback ๊ฒ€ํ†  ํ (๊ด€๋ฆฌ์ž๊ฐ€ ์Šน์ธํ•œ trusted ๋ ˆ์ฝ”๋“œ๋งŒ ์„ฑ๊ณต SQL ํ•™์Šตยท๋ฃฐ ์Šน๊ฒฉ์— ์žฌ์‚ฌ์šฉ) |
| `audit/*.jsonl` | ๋ชจ๋“  tool call ๊ฐ์‚ฌ ๋กœ๊ทธ (์ž๋™ ๊ธฐ๋ก, git ์ œ์™ธ) |

## Evaluation

```sh
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

1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ `record_feedback`์œผ๋กœ ์งˆ๋ฌธ/SQL/๊ฒ€์ฆ์˜ค๋ฅ˜/๊ต์ •๋ณธ/์ฑ„ํƒ์—ฌ๋ถ€๋ฅผ ๊ฒ€ํ†  ํ์— ์ €์žฅ
2. ๊ด€๋ฆฌ์ž๊ฐ€ `review_feedback`์œผ๋กœ ๋‚ด์šฉ๊ณผ ๋ฒ”์œ„๋ฅผ ํ™•์ธํ•ด approve/reject
3. ์Šน์ธ๋œ trusted ์„ฑ๊ณตยท๊ต์ • SQL๋งŒ ์ฆ‰์‹œ few-shot ์˜ˆ์ œ์™€ ๊ฒ€์ƒ‰ ๋ถ€์ŠคํŠธ์— ๋ฐ˜์˜
4. `learn_from_feedback` ํ˜ธ์ถœ(๋˜๋Š” ์ฃผ๊ธฐ ์‹คํ–‰) ์‹œ ์Šน์ธ๋œ ํ”ผ๋“œ๋ฐฑ์˜ ๋ฐ˜๋ณต ํŒจํ„ด์„ ๋ฃฐ๋กœ ์Šน๊ฒฉ:
- `recurring_error` โ€” ๊ฐ™์€ ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ NํšŒ ์ด์ƒ โ†’ ํ•ด๋‹น ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ ์‚ฌ์šฉ ์‹œ ์˜ˆ๋ฐฉ ๊ฒฝ๊ณ 
- `table_correction` โ€” ๊ต์ •์—์„œ ๋ฐ˜๋ณต์ ์œผ๋กœ ๊ต์ฒด๋œ ํ…Œ์ด๋ธ” โ†’ ๊ฒ€์ƒ‰ ์ ์ˆ˜ ํŒจ๋„ํ‹ฐ + ๊ฒฝ๊ณ 
- `column_correction` โ€” ๋ฐ˜๋ณต ๊ต์ฒด๋œ ์ปฌ๋Ÿผ โ†’ validate_sql์ด ๋Œ€์ฒด ์ปฌ๋Ÿผ ํžŒํŠธ ์ œ์‹œ
- `slow_query` / `recurring_exec_error` โ€” ์‹คํ–‰ ๊ฐ์‚ฌ ๋กœ๊ทธ์—์„œ ๋ฐ˜๋ณต ์ง€์—ฐยท์˜ค๋ฅ˜(PG-*/MY-*/TIMEOUT) ์Šน๊ฒฉ
5. ๋ฃฐ์€ `learned_rules.json`์œผ๋กœ ์˜์†ํ™”; ์„œ๋ฒ„ ์žฌ๊ธฐ๋™ ์‹œ ์ž๋™ ์ ์šฉ, ์šด์˜์ž๊ฐ€ ์ง์ ‘ ํŽธ์ง‘ ๊ฐ€๋Šฅ

## SQL Generation Flow

1. `analyze_question` โ†’ ๋ชจํ˜ธ์„ฑ ํ™•์ธ (๊ธฐ๋ณธ๊ฐ’ ์ ์šฉ ์‹œ ๊ฐ€์ • ํ‘œ์‹œ), ํŒจํ„ดยทintent ์‹œ๊ทธ๋‹ˆ์ฒ˜ ํ™•๋ณด
2. `search_schema` (+`find_filter_columns`, `resolve_time`)
3. `get_metric_definition` โ€” ์—…๋ฌด ์ง€ํ‘œ๋Š” ์‚ฌ์ „ expression๋งŒ ์‚ฌ์šฉ
4. `get_schema_context` โ€” ์••์ถ• ์ปจํ…์ŠคํŠธ๋งŒ LLM์— ์ „๋‹ฌ
5. `get_join_paths` โ€” ON ์กฐ๊ฑด์€ ๋ฐ˜๋“œ์‹œ ์—ฌ๊ธฐ์„œ ์ทจ๋“; ๊ฒฝ๋กœ ์—†์Œ/์ €์‹ ๋ขฐ ์‹œ ๋˜๋ฌป๊ธฐ
6. ๋ณต์žก/๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์งˆ๋ฌธ์ด๋ฉด `build_sql_skeleton`์œผ๋กœ ๊ณจ๊ฒฉ ํ™•๋ณด ํ›„ SLOT๋งŒ ์ฑ„์›€; ๋‹จ์ˆœ ์งˆ๋ฌธ์€ ์ง์ ‘ ์ƒ์„ฑ (์ปจํ…์ŠคํŠธ ๋‚ด ์‹๋ณ„์ž๋งŒ, PII ๊ธˆ์ง€, row bound(LIMIT) ํ•„์ˆ˜)
7. `validate_sql` (`expected_outputs`, `metrics` ์ „๋‹ฌ) โ€” fix_hints ๋ฐ˜์˜ ์ตœ๋Œ€ 2ํšŒ ์žฌ์‹œ๋„; ์‹คํŒจ SQL ์‹คํ–‰ ๊ธˆ์ง€. ๋‚œ์ด๋„ ๋†’์€ ์งˆ๋ฌธ์€ ํ›„๋ณด 2~3๊ฐœ๋ฅผ ๋งŒ๋“ค์–ด `rank_candidates`๋กœ ์ตœ์„ ์•ˆ ์„ ํƒ
8. `explain_sql` โ€” risk=high๋ฉด ๊ธฐ๊ฐ„/limit ์กฐ๊ฑด ์ถ”๊ฐ€ ํ›„ ์žฌ์ƒ์„ฑ (`profile` ์ง€์ • ์‹œ ์‹ค์ธก EXPLAIN)
9. ๊ตฌ์กฐํ™” JSON ์‘๋‹ต (sql, ์‚ฌ์šฉ ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ, ์ง€ํ‘œ, ์กฐ์ธ, ํ•„ํ„ฐ, ๊ฐ€์ •, ์ฃผ์˜, ๊ฒ€์ฆ๊ฒฐ๊ณผ, ์‹คํ–‰๊ฐ€๋Šฅ์—ฌ๋ถ€)
10. `record_feedback`