用 Rust + GPUI 做了一个开源数据库客户端:FluxDB
最近在做一个开源桌面项目 FluxDB:用 Rust 和 GPUI,把 SQL 查询、表数据编辑、结构管理和 Redis 浏览放进同一个应用。
目前支持 MySQL、TiDB、PostgreSQL、SQLite 和 Redis,提供 macOS、Windows 和 Linux 安装包。
数据库客户端并不是新方向,DBeaver、DataGrip、Navicat 等工具已经很成熟。我想分享的也不是"又一个工具要替代谁",而是:选择 Rust + GPUI 这条原生桌面路线后,一个数据库客户端需要处理哪些问题。
项目地址:github.com/fluxdb-alt/...

先说现在能做什么
FluxDB 目前的基础功能包括:
- 查询与编辑 SQL:语法高亮、代码折叠、表列补全、格式化和查询历史。
- 浏览和修改数据:分页、筛选、单元格编辑、数据导出与 SQL 文件执行。
- 管理表结构:查看列和 DDL,创建、修改表。
- 查看 ER 关系:展示数据库外键,也可以手动记录本地逻辑关系。
- 操作 Redis:浏览和编辑键值,使用工作台和命令终端。
- 多连接工作区:通过侧边栏和标签页切换数据库对象,支持明暗主题。
这些功能不是在每种数据库上完全对等。例如 Redis 的操作对象是键值,而不是关系型数据库里的表;各数据库的实际支持范围可以看项目说明和操作手册。
下面聊几个实现上的取舍。
1. 选了原生 UI,不代表性能问题就自动解决了
FluxDB 的界面使用 GPUI,也就是 Zed 所使用的 UI 框架,不通过 Electron 或 WebView 绘制。
选择这条路线,意味着界面、状态和数据库相关逻辑都可以围绕 Rust 组织,也能复用 GPUI 生态里的表格、输入框和弹层等组件。
但"使用原生框架"不等于"应用天然流畅"。
对于数据库客户端,阻塞可能来自很多地方:
- 展开数据库节点时,一次读了太多元数据。
- 查询和数据提交占用了界面处理路径。
- 切换标签页后,上一次请求的结果才返回。
- 每次打开关系图,都重新读取同一份表结构。
所以,比框架标签更重要的是:界面需要什么数据、什么时候读取、结果回来后还应不应该使用。
目前我没有提供与其他客户端的统一性能基准,也不会仅凭 Rust + GPUI 就宣称启动更快、内存更低。这样的结论需要明确的环境、数据集和测量方法。
2. 点击"保存"之后,不能只剩一个数据库调用
以表格编辑为例,用户修改一个单元格,看起来只是输入了一个新值。真正提交时,还需要处理等待状态、失败反馈,以及数据库写入后如何刷新界面。
FluxDB 把这类工作分成几层,简化来看是:
text
桌面界面:渲染、输入、交互
|
AppCommand
|
应用层:状态与业务编排
|
Connector 接口
|
具体数据库实现
结果再通过应用状态和事件回到界面。领域模型与 Connector 契约放在 Core,持久化由独立的 Storage 层负责。
这样做不只是为了把目录分得整齐。
例如数据提交,应用层把它分成准备、后台执行、结果回填几个阶段:
- 先进入提交状态,让界面及时显示正在保存,并阻止重复提交。
- 在后台完成数据库写入,以及需要的重新加载。
- 把结果交回界面:成功时同步原始数据与改动状态;失败时显示错误,保留尚未成功提交的改动。
另一个细节是,"写入失败"和"写入后的刷新失败"不能混成同一个错误。
后一种情况下,数据可能已经写入数据库。如果用户把它理解为"没有保存成功"而重复提交,就可能产生不必要的写入。因此,相关错误需要区分保存与刷新阶段,而不是统一弹一个"操作失败"。
类似地,数据库之间也不能简单套同一份逻辑。项目里的 PostgreSQL 数据提交会结合原始页面值构造保护条件;这不意味着所有数据库、所有查询结果都拥有相同的并发保护能力。
做数据库工具时,"能把新值写进去"只是开始,把失败语义解释清楚同样重要。
3. 元数据也需要按需加载
我们很容易注意到查询结果有多少行,却忽略表结构和关系信息也有规模问题。
打开一个库时,可能需要读取表名、注释、字段、主键、外键。把这些信息一次性全部读完,再交给 UI,并不总是合适的。
FluxDB 的 ER 画布采用分阶段加载:
- 先读取表目录,让画布可以展示表节点。
- 按视口需要读取字段。
- 后台加载关系索引,再补充连线与关系布局。
字段和关系数据由应用层缓存,不让每个标签页各读一遍。同一份元数据的在途请求也会去重,数据库查询放在缓存锁之外。
缓存还涉及一个容易被忽略的问题:名称不是完整身份。
PostgreSQL 的不同 Schema 可以有同名表,标识符里也可能包含点号。如果直接把界面上的展示名称当作唯一键,或者用字符串切分反推 Schema,就很容易混淆对象。
因此,这里的缓存键不仅包含表名,还包含连接、连接修订、数据库和 Schema 等信息。连接配置发生变化后,旧修订下的结果不能继续当作当前数据使用。
这是一个比"加个缓存"更具体的约束:既要少读数据库,也不能为了复用而把旧结果用错地方。
4. "关系图上的一条线"不一定是数据库约束
关系图还有一个语义问题:数据库声明的外键,与用户在客户端记录的业务关系,不应该混在一起。
FluxDB 将两者区分:
- 数据库外键来自数据库元数据。
- 本地逻辑关系由用户手动建立并确认,保存在 FluxDB 中。
本地逻辑关系可以帮助梳理表之间的联系,但不会因为画了一条线,就执行 DDL、创建数据库外键,或者让数据库开始强制校验数据完整性。
关系是否经过确认、当前引用的表和字段是否仍然有效,也要单独记录。结构变化后,失效的关系不能继续作为有效连线展示。
这类功能的难点不只是连线怎么画,而是:界面表达的含义,不能超过底层事实。
5. 跨平台不只是编译出三个可执行文件
FluxDB 提供 macOS 的 ARM64/Intel DMG、Windows x64 安装器,以及 Linux x64 DEB。
但生成安装包只是其中一步,还要处理:
- 图标、资源目录与桌面启动项。
- 各平台原生构建依赖和动态库。
- Windows 下外部子进程的控制台窗口。
- macOS 签名、公证与首次启动拦截。
- Linux 发行版依赖与图形环境差异。
项目通过 GitHub Actions 为不同平台构建和打包,并生成 SHA-256 校验文件。macOS 的打包脚本还需要处理应用资源和动态库。
这里也需要区分两个结论:有对应平台的安装包,不代表已经覆盖所有硬件与系统环境。
目前 macOS 安装包采用 ad-hoc 签名,尚未公证;Linux 提供的是 DEB,并没有同时覆盖 AppImage、Flatpak 等分发形式。遇到兼容性问题时,具体的系统版本和架构信息很有帮助。
关于 AI,以及项目现在的边界
项目开发中大量使用 AI,代码生成主要由 AI 完成,人工负责需求定义、变更审阅和验收。这不是所有实现都已经可靠的证明,尤其是数据库工具,还需要持续核实写入、安全和异常处理的边界。
同时,"用 AI 开发项目"和"产品里提供 AI 功能"是两件事。
本文的已发布基础能力以 v0.1.7 为参考。开发分支和演示视频中包含 AI 助手、分析报告等探索,但 v0.1.7 的 AI 入口尚未开放,不能据此认为下载该版本就能使用视频中的全部功能。
后续发布时,功能范围以对应 Release 说明为准。
这也是目前需要继续做好的事情:不仅完善功能,也让安装包、文档和演示保持一致。
下载与反馈
如果你正在尝试 Rust 原生桌面开发,或者想试试另一个开源数据库客户端,欢迎看看 FluxDB:
项目使用 GPL-3.0-or-later 许可证,当前界面主要为中文,仍处于早期阶段。建议先用测试库试用,操作重要数据前做好备份。
比起一句"好不好用",我更希望收到具体反馈:哪个系统上安装失败、哪种数据库连接有问题,或者哪个操作和你的预期不同。提交 Issue 时附上应用版本、系统与数据库版本,以及脱敏后的复现步骤,会更容易定位。
如果这些取舍也对你做桌面工具有所帮助,欢迎交流;如果 FluxDB 对你有用,也欢迎 Star 或参与贡献。