内容
# Quarry
> **为 AI 时代打造的数据库工作台** — 一个内核,多张面孔(CLI / GUI / MCP / 代理技能)。
[](https://github.com/Wangggym/quarry/actions/workflows/ci.yml)
[](TESTING.md)
[](TESTING.md)
[](https://pypi.org/project/quarry-db/)
[](https://pypi.org/project/quarry-db/)
[](LICENSE)
[中文文档 →](README.zh-CN.md) · [官网 →](https://quarry.yiminlab.site)

你所知道的每个数据库工具 — DBeaver、TablePlus、pgAdmin — 都假设在键盘上有一个**人类**。但越来越多的情况下,执行查询的实体是一个**AI 代理**,而代理需要不同的保证:
- **机器可以解析的结果**,而不是人类可以阅读的屏幕
- **内核中存在的安全防护**,以便没有客户端可以忘记它们
- **确定性的错误合约**(稳定的退出代码),而不是用于抓取的堆栈跟踪
- **配置文件**,而不是点击 — 以便可以对其进行版本控制、差异比较和与代理共享
Quarry 颠覆了传统设计:它是一个**具有代理安全合约的查询内核**,而人类面孔(CLI、GUI)是从同一内核中衍生出来的薄壳。无论查询来自浏览器中的人员、CI 中的脚本还是运行技能的 Claude,它都会经过相同的安全防护并返回相同的结构化结果。
## 哲学
1. **一个核心,多张面孔。** 连接管理、查询执行、模式 introspection 和安全防护都存在于一个可导入的内核(`quarry.core`)中。CLI(`qy`)、GUI、MCP 服务器和代理技能都是薄壳。一次修复 bug,所有面孔都会得到修复。
2. **默认只读;升级是显式且有步骤的。** 除非传递 `--write`,否则写入和 DDL 操作会被阻止(退出代码 `8`)。生产环境连接需要在 `--write` 的基础上进行额外的确认。每个查询都会自动添加 `LIMIT 500`,除非你选择退出。因为安全防护在内核中,所以代理无法通过选择不同的入口点来绕过它们。
3. **机器可以信任的合约。** 每个查询都返回 `{columns, rows, rowCount, truncated, elapsedMs, engine, sql}`。退出代码是稳定的 API:`0` 表示正常,`2` 表示连接错误,`3` 表示 SQL 错误,`8` 表示安全阻止。代理可以根据结果分支,而无需解析文本。
4. **工作区即代码。** 工作区只是一个目录:`connections.toml` + `queries/**/*.sql`(带有 `-- @meta` 头部的命名查询)。它存在于**你的** 仓库中,由 git 进行版本控制,并可以在团队成员和代理之间共享。内核本身不包含任何业务逻辑和机密。
5. **几乎零依赖。** 纯标准库。PostgreSQL 通过系统 `psql` 访问,Redis 通过 `redis-cli` 访问,SSH 隧道通过系统 `ssh` 访问。MySQL 是一个可选的 `pymysql`。没有 Electron,没有守护进程,没有云。
## 安装
```bash
pipx install quarry-db # 或:pip install quarry-db
qy --help
```
PostgreSQL 使用系统 `psql` 二进制文件;MySQL 需要 `pip install "quarry-db[mysql]"`。
## 快速开始
```bash
mkdir my-workspace && cd my-workspace
cat > connections.toml <<'EOF'
[shop]
url = "postgresql://user:pass@localhost:5432/shop"
engine = "postgres"
env = "dev"
EOF
qy connections # 列出连接
qy exec shop --sql "select * from customers"
qy schema shop customers # 表结构 (\d+)
qy gui # 浏览器数据网格
```
## 工作区
工作区目录是连接和查询的源:
```
my-workspace/
├── connections.toml # [key] url / engine / env / group / notes
└── queries/<db>/*.sql # 命名查询(带有 -- @meta 头部)
```
解析顺序:`--workspace PATH` → `~/.config/quarry/config.toml` → 当前目录。
## CLI 参考
| 命令 | 目的 |
|---------|---------|
| `qy connections [list\|add\|set\|remove\|test]` | 管理连接 |
| `qy exec <db> --sql "..." [--format json\|ndjson\|csv\|table]` | 运行临时 SQL |
| `qy schema <db> <table>` | 实时表结构 |
| `qy run <name> [k=v ...]` | 运行已保存的命名查询 |
| `qy save <name> --db X --sql "..."` | 保存命名查询 |
| `qy list / describe / validate / fingerprint / audit` | 管理命名查询 |
| `qy workspace list/add/remove` | 管理聚合工作区 |
| `qy local up/down/status/sync [--engine postgres\|redis\|all]` | 本地开发容器(见下文) |
| `qy gui` | 启动本地 GUI |
| `qy mcp [--write]` | 通过 stdio 为 AI 代理提供 MCP 接口(用于 AI 代理) |
## MCP(面向代理的原生接口)
`qy mcp` 通过 stdio 提供模型上下文协议 — 纯标准库,无需 SDK 依赖。代理获得六种工具(`list_connections`、`list_tables`、`describe_table`、`exec_sql`、`list_saved_queries`、`run_saved_query`),具有相同的安全防护:除非服务器启动时添加 `--write` **且** 调用传递 `write: true`,否则默认为只读;生产环境还需要 `confirm_prod: true`。
```bash
# Claude 代码
claude mcp add quarry -- qy mcp --workspace ~/my-workspace
```
```json
// 或任何 MCP 客户端(.mcp.json)
{ "mcpServers": { "quarry": { "command": "qy", "args": ["mcp", "--workspace", "/path/to/workspace"] } } }
```
已在 [MCP 注册表](https://registry.modelcontextprotocol.io/) 上发布,名称为 `mcp-name: io.github.Wangggym/quarry`。
## 安全防护(AI 原生护城河)
- **默认只读**:写入/DDL 操作被阻止,退出代码为 `8`;使用 `--write` 允许
- **自动行限制**:`run_query()` 注入 `LIMIT 500`;使用 `--max-rows N` 提升
- **逐步生产保护**:所有环境默认为只读 → 开发环境需要 `--write` → 生产环境需要 `--write` **且** 交互式确认(`--yes` 用于自动化)
- **稳定的退出代码合约**:`0` 正常 / `2` 连接 / `3` SQL / `8` 安全阻止
## 作为库(GUI 和代理使用)
```python
from quarry import configure_workspace, get_connection, run_query
configure_workspace("~/my-workspace")
res = run_query(get_connection("shop"), "select * from customers")
print(res.to_dict()) # {columns, rows, rowCount, truncated, elapsedMs, engine, sql}
```
## SSH 隧道
对于只能通过堡垒机访问的数据库,请添加 `ssh_*` 字段,`qy` 会自动打开隧道(系统 `ssh`,零依赖):
```toml
[internal_db]
url = "postgresql://user:pass@127.0.0.1:5432/appdb"
engine = "postgres"
ssh_host = "bastion.example.com"
ssh_user = "ubuntu"
ssh_key = "~/.ssh/id_ed25519"
```
## Redis
`engine = "redis"`(使用系统 `redis-cli`)。查询是 redis 命令:
```bash
qy exec cache --sql "SCAN 0 COUNT 100"
qy exec cache --sql "HGETALL user:42"
```
只读防护也适用:`GET/SCAN/TYPE/TTL/HGETALL` 通过;`SET/DEL/FLUSHALL` 在没有 `--write` 的情况下被阻止。在 GUI 中,redis 键是可点击的,具有 TYPE 感知值显示。
## 分组和环境集
连接可以组织成 **项目文件夹**(`group`)和 **环境集**(相同的 `db`,不同的 `env`,共享模式):
```toml
[shop_dev]
url = "postgresql://…dev…/shop"; group = "shop"; db = "shop"; env = "dev"
[shop_prod]
url = "postgresql://…prod…/shop"; group = "shop"; db = "shop"; env = "prod"
```
- 具有相同 `db` 的连接折叠成一个环境集 — 一个保存的查询可以针对任何环境运行:`qy exec shop --env prod`
- 未指定的环境默认为 `dev`(最安全)
- GUI 显示环境切换器(生产环境变为红色)
## 多个工作区
`qy` 聚合 `~/.config/quarry/config.toml` 中列出的所有工作区 — 一个 GUI/CLI 跨越所有项目:
```bash
qy workspace add ~/projects/acme/db-workspace
qy workspace add ~/projects/side-project/db
qy connections # 两个项目,分组
qy gui # 侧边栏显示两个组并排
```
`--workspace a:b`(以 `os.pathsep` 分隔)作为临时覆盖;第一个目录是用于写入的主要目录。
## 本地开发容器
当本地运行的服务共享远程(开发)数据库时,每个读写操作都会跨越公共网络 — 而测试/e2e 运行大量访问数据库的测试会在往返途中变得不稳定。`qy local` 在 docker 容器中运行 Postgres/Redis,以便服务仅与 `localhost` 通信:
```bash
qy local up shop # 启动本地 Postgres + 注册一个 shop `local` 连接
qy connections # shop 现在显示一个 [local] 环境,旁边是 [dev]
qy run active_customers --env local
qy local status # 运行中?哪个端口 / 镜像?
qy local sync shop # 将开发模式复制到本地(分段数据库 + 重命名交换)
qy local down # 停止,保持数据卷(数据存活)
qy local down --purge # 停止 + 删除卷(下次启动时为空数据库)
```
一个共享的 Postgres 容器托管每个连接键的逻辑数据库(固定端口 `5433`;redis `6380`),数据存活于一个命名的 docker 卷中。需要 docker 守护进程;镜像标签可通过 `--image` 覆盖。
## GUI

`qy gui` — 一个本地的、零构建的 web GUI(Slate & Copper 主题,浅色/深色):
- 分组的侧边栏树,带有环境切换器(生产环境变为红色),连接健康指示点
- **多标签编辑器** — 每个标签记住其 SQL + 连接,重启后仍保持不变
- SQL 高亮 + 本地自动补全(关键字 / 表 / 列)
- **EXPLAIN 按钮** — 一键获取查询计划
- 类型感知数据网格:排序,列大小调整,**键盘导航**(箭头 + Enter),单元格检查,带有 **可折叠 JSON 树**
- CSV/JSON 导出,**可搜索的查询历史**(带有连接 + 时间)
- TYPE 感知 Redis 键浏览
- **更新检查** — 一个后台线程每 24 小时轮询 PyPI 一次,并在有更新时显示标题徽章(带有升级命令 + 发布说明)。可编辑/开发安装会自动跳过;设置 `QUARRY_UPDATE_CHECK=0` 可完全禁用它。
## 路线图
- 所有引擎的结果合约中的列类型
- SQLite 和 DuckDB 引擎(零设置本地演示)
- Redis 键命名空间折叠树
- 跨环境模式/数据差异
- 写审计日志(谁运行了什么,在哪里,什么时候)
- 单二进制分发
## 开发和测试
```bash
pip install -e ".[dev]"
createdb quarry_test && psql quarry_test -f tests/seed.sql # 或:make seed
make test # 分层运行,带有每层的 PASS/FAIL 总结
```
**四层 723 个测试**,每个测试自动分类,以便您可以运行任何切片:
| 层 | 计数 | 覆盖 | 需要 |
|-------|------:|--------|-------|
| `unit` | 568 | 纯逻辑 + 模拟引擎(安全防护,SQL 骨架,参数,格式化器,缓存) | 无 |
| `integration` | 110 | 进程内针对真实数据库,包括 GUI HTTP API 和 CLI/MCP 调度 | Postgres |
| `e2e` | 45 | 真实的 `qy` CLI 和 `qy mcp` stdio 服务器作为子进程 | Postgres |
| `browser` | 20 | **真实 GUI 前端** 由无头 Chromium 驱动(Playwright) | Postgres + Playwright |
数据库/引擎支持的测试在引擎不可访问时会自动跳过,因此测试套件在裸机上保持绿色;CI 提供引擎并运行所有测试。
**覆盖率 ≥95%**(单元 + 集成)目前为 **99.6%**。
### 一目了然地查看测试状态
- **在 GitHub 上:** 上方的 CI 徽章是实时的 — 如果任何层或覆盖率门槛失败,它会变为红色。每次提交和每次 PR 的结果显示在 **Actions** 标签下和 PR 检查中。
- **本地,成功/失败:** `make test` 打印彩色的每层总结;使用 `make test-unit` / `test-integration` / `test-e2e` / `test-browser` 运行某一层。
- **本地,覆盖率:** `make cov` 强制执行门槛并写入 HTML 报告 — 打开 `htmlcov/index.html` 以查看逐行视图。
参见 [TESTING.md](TESTING.md) 以获取完整的架构、fixtures 和 CI 布局,并参见 [CONTRIBUTING.md](CONTRIBUTING.md) 以获取贡献指南。
Quarry 在 macOS 和 Linux 上开发和测试。Windows 目前未经测试(psql/ssh 集成和端口占用是 Unix-flavored 的)— 欢迎提交 PR。
## 许可证
[MIT](LICENSE)