用 Vibe Coding 从零做出 QueryEngine:一条可嵌套查询引擎的实战笔记

最近我用「Vibe Coding」——和 AI 结对、用自然语言驱动实现——从零做了一套可嵌入的查询引擎 QueryEngine。它不是再包一层现成引擎,而是自研运行时:同一套引擎同时支持 NestQL(自有嵌套 JSON 协议)、GraphQL、RESTful。

仓库:https://github.com/AwenJackson/queryengine
文档站:https://queryengine-docs.netlify.app

这篇文章想分享三件事:怎么用 Vibe Coding 把这件事做完、过程里踩坑与经验,以及 QueryEngine 现在的优缺点。

为什么要做 QueryEngine

业务里经常需要「一次请求嵌套多张表」:用户 → 动态 → 评论,前端不想拼一堆 REST。市面上有类似「JSON 嵌套查库」的思路,但我这边约束很明确:

  • 从零自研,不依赖、不拷贝第三方查询引擎源码
  • 协议形似常见嵌套 JSON 写法,但产品名与协议名是自有的 NestQL
  • 同时要有 GraphQL 和 RESTful,三条入口共用注册表、方言和权限
  • 无会话鉴权;表权限按 schema 懒加载;表名大小写不敏感,列名与物理列一致
  • 不做「查询深度 / 对象数」那类硬上限,边界交给超时和连接池

技术栈:JDK 21、Spring Boot 3.5.3、Maven,方言先做 MySQL 与达梦。

Vibe Coding 怎么跑通这条线

我的工作流大致是:

  1. 先定契约,再写代码
    先说清楚:get/head/post/put/delete、条件运算符、@column / @order、key@ 引用、数组分页软限制。AI 负责实现,我负责验收「样例能不能跑通」。

  2. 模块边界写进提示词
    core / 方言 / spring-boot-starter / web / boot / demo 一开始就拆开。扩展点可继承重写(解析、SQL 规划、注册表、表名映射等),避免后面变成「一个大类改不动」。

  3. 用 PR 节奏约束 AI
    骨架 → MVP → 方言与金样 → HTTP 与错误码 → GraphQL/REST → 文档站。每步可合并、可回滚,比「一次生成十万行」稳得多。

  4. 文档和代码一起交付
    VitePress 文档站写全 NestQL 运算符、读写、引用与软限制,并补 GraphQL / REST 语法。文档地址见上。线上文档比「仓库里有一份 protocol.md」对使用者友好得多。

  5. 发布策略先本地,不硬冲 Central
    日常 mvn install 进本地仓库,其他项目用 BOM + 模块依赖接入。Maven Central / JitPack 可以以后再做,先保证「能嵌、能调、能讲清楚」。

经验(可直接抄走)

  • 把「不要什么」写进提示词:例如不拷第三方源码、不要会话、不要深度硬上限、列名不自动驼峰。AI 很会「帮你补全成业界默认」,默认往往和你的产品决策冲突。
  • 金样 > 感觉:嵌套引用路径(/User/id、[]/User/id)这类细节,靠样例回归比靠聊天对线靠谱。
  • 三条入口共用一个引擎:否则 NestQL 能查、GraphQL 行为漂移,文档和维护都会炸。
  • 文档站要写全语法:只写「简介 + 看仓库 protocol」等于没写;运算符表必须挂在站点侧栏里。
  • BOM 是 pom 不是 jar:queryengine-bom 不会打出 jar,这是正常的;接入方 import BOM 再声明具体模块。

QueryEngine 优点

  1. 一套引擎,三种接入:NestQL 适合嵌套 JSON;GraphQL 适合字段裁剪;REST 适合简单 CRUD。
  2. 可嵌入:queryengine-core 可不要 Web;starter 只出 Bean;需要 HTTP 再加 queryengine-web。
  3. 扩展点清晰:解析、规划、注册表、表名映射可继承替换,适合接到现有 CMS / 中台。
  4. 方言可插拔:MySQL / 达梦;schema 动态、access 按库懒加载。
  5. 协议文档公开:运算符、读写、引用、软限制在文档站可查。

QueryEngine 缺点 / 边界

  1. 不是「完整形似协议的 100% 互通」:明确不做 @raw、JOIN 矩阵、远程函数、存储过程、query=0/1/2 组合语义等;LIKE 不自动补 %,@order 只用 + / -。
  2. 关联是引用再查,不是 JOIN:深嵌套会放大 SQL 次数,要靠超时与分页软限制约束,而不是协议层硬砍深度。
  3. 无登录体系:适合内网或由宿主做网关鉴权;直接裸奔公网不合适。
  4. 对象查询多行会失败:要列表必须用 [] / 表名[],和「默默取第一行」的习惯不同。
  5. 公网坐标尚未走 Central:目前推荐本机 mvn install 或同源私服;团队协作要自己约定制品分发。
  6. Vibe Coding 的代价:提示词和验收标准一旦漂移,AI 会「好心」加回你砍掉的能力(例如硬上限、Central 发布),需要人盯产品边界。

谁适合试

  • 想在 Java / Spring Boot 里嵌一套「嵌套 JSON + GraphQL + REST」统一查询层
  • 能接受「形似 + 自有扩展」,不以第三方客户端 100% 兼容为目标
  • 愿意用文档站和金样做契约,而不是口头约定

不适合:需要完整 JOIN / 远程函数 / 开箱即用的公网 Central 坐标、或必须零运维的「点一下就全球依赖」的场景(现阶段)。

NestQL 查询样例(多层嵌套)

一次 POST http://127.0.0.1:8080/get,从用户 38710 一路嵌到动态、评论、类型、书、章节与 tip。关联靠 key@ 引用,不是 JOIN。

请求

▼
http
复制代码
POST http://127.0.0.1:8080/get Content-Type: application/json
▼
json
复制代码
{ "User": { "id": 38710 }, "Moment[]": { "Moment": { "userId@": "User/id" }, "Comment[]": { "Comment": { "momentId@": "[]/Moment/id" }, "Type[]": { "Type": { "comment_id@": "[]/Comment/id" }, "Book[]": { "Book": { "type_id@": "[]/Type/id" }, "Book_menu[]": { "Book_menu": { "book_id@": "[]/Book/id" }, "Book_menu_tip[]": { "Book_menu_tip": { "book_menu_id@": "[]/Book_menu/id" } } } } } } } }

响应(节选说明 + 完整结果)

HTTP 成功时顶层带 code / message。Type[]、Book[] 等为空数组表示该层没有命中行,不是报错。

▼
json
复制代码
{ "User": { "id": 38710, "name": "Tommy", "sex": 0, "tag": "student" }, "Moment[]": [ { "Moment": { "id": 1, "userId": 38710, "content": "hello" }, "Comment[]": [ { "Comment": { "id": 10, "momentId": 1, "userId": 82001, "content": "nice" }, "Type[]": [] }, { "Comment": { "id": 11, "momentId": 1, "userId": 70793, "content": "good" }, "Type[]": [ { "Type": { "id": 3, "user_id": 70793, "type_name": "删除", "comment_id": 11 }, "Book[]": [ { "Book": { "id": 1, "user_id": 70793, "book_name": "一千零一夜", "book_desc": "一千零一夜的描述", "type_id": 3 }, "Book_menu[]": [ { "Book_menu": { "id": 3, "user_id": 70793, "menu_name": "章节一", "book_id": 1 }, "Book_menu_tip[]": [ { "Book_menu_tip": { "id": 3, "book_menu_id": 3, "tip_name": "嘿嘿哈哈" } } ] } ] } ] } ] }, { "Comment": { "id": 13, "momentId": 1, "userId": 38710, "content": "aoisa" }, "Type[]": [ { "Type": { "id": 1, "user_id": 38710, "type_name": "类型名称", "comment_id": 13 }, "Book[]": [ { "Book": { "id": 2, "user_id": 38710, "book_name": "安徒生童话", "book_desc": "安徒生童话的描述", "type_id": 1 }, "Book_menu[]": [ { "Book_menu": { "id": 1, "user_id": 38710, "menu_name": "章节一", "book_id": 2 }, "Book_menu_tip[]": [ { "Book_menu_tip": { "id": 1, "book_menu_id": 1, "tip_name": "嘿嘿" } } ] }, { "Book_menu": { "id": 2, "user_id": 38710, "menu_name": "章节二", "book_id": 2 }, "Book_menu_tip[]": [ { "Book_menu_tip": { "id": 2, "book_menu_id": 2, "tip_name": "哈哈" } } ] } ] } ] }, { "Type": { "id": 2, "user_id": 38710, "type_name": "新增", "comment_id": 13 }, "Book[]": [] } ] } ] }, { "Moment": { "id": 2, "userId": 38710, "content": "world" }, "Comment[]": [ { "Comment": { "id": 12, "momentId": 2, "userId": 82001, "content": "ok" }, "Type[]": [] } ] } ], "code": 200, "message": "success" }

这条样例也说明了 NestQL 的两个产品点:列名保持物理列写法(如 comment_id、type_id),表名大小写不敏感;深嵌套没有协议层硬深度上限,实际成本是多次按引用查询。

结语

Vibe Coding 不是「不用写代码」,而是把精力挪到:产品边界、契约样例、模块边界、文档与验收。QueryEngine 这条线里,AI 把骨架和样板推得很快;真正决定能不能用的,是你有没有把「不要什么」和「怎样算对」写死。

欢迎看文档、提 Issue,也欢迎吐槽缺点——缺点写清楚,比吹一波更有用。


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP