# toml-db-release **Repository Path**: jericho-yu/toml-db-release ## Basic Information - **Project Name**: toml-db-release - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-15 - **Last Updated**: 2026-04-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # toml-db-kernal 用户指南 --- ## 目录 1. [第一部分:概念说明](#第一部分概念说明) - [1.1 核心架构与执行流](#11-核心架构与执行流) - [1.2 存储模型(Append-Only)](#12-存储模型append-only) - [1.3 热点数据逻辑](#13-热点数据逻辑) - [1.4 压缩逻辑与策略(COMPACT)](#14-压缩逻辑与策略compact) - [1.5 关系建模亮点:`REFERENCES ->` 与 `<-`](#15-关系建模亮点references---与--) - [1.6 嵌套响应格式与传统数据库差异](#16-嵌套响应格式与传统数据库差异) 2. [第二部分:使用说明](#第二部分使用说明) - [快速启动](#1-快速启动) - [认证与连接](#2-认证与连接) - [管理页面](#3-管理页面) - [WebSocket SQL 协议](#4-websocket-sql-协议) - [参数绑定](#5-参数绑定) - [建表(DDL)](#6-建表ddl) - [表结构变更](#7-表结构变更) - [数据查询(SELECT)](#8-数据查询select) - [数据写入(INSERT)](#9-数据写入insert) - [数据更新(UPDATE)](#10-数据更新update) - [数据删除(DELETE)](#11-数据删除delete) - [JOIN 多表连接(重点)](#12-join-多表连接重点) - [PRELOAD 关系预加载](#13-preload-关系预加载) - [子查询](#14-子查询) - [权限系统与用户管理](#15-权限系统与用户管理) - [存储原理与运维](#16-存储原理与运维) - [启动模式、日志与终端 SQL 控制台](#17-启动模式日志与终端-sql-控制台) - [toml-db-driver 使用示例](#18-toml-db-driver-使用示例) - [附录 A:HTTP 统一响应格式](#附录-ahttp-统一响应格式) - [附录 B:发布说明](#附录-b发布说明) --- ## 第一部分:概念说明 ### 1.1 核心架构与执行流 `toml-db-kernal` 的核心链路可以简化为: 1. 协议层接收 SQL(WebSocket / 控制台 / MySQL 协议适配) 2. 解析层将 SQL 转换为结构化语句(含 WHERE/JOIN/PRELOAD/子查询) 3. 引擎层执行语义(查询、写入、DDL、压缩) 4. 存储层按 Append-Only 规则落盘 5. 格式层输出响应(TOML/JSON,内部以结构化数据流转) 这意味着:**解析、执行、存储、输出** 是分层解耦的;你看到的 SQL 语法能力,最终都会映射到引擎与存储规则。 ### 1.2 存储模型(Append-Only) TOML-DB 不是“原地改页”模型,而是“追加版本”模型: - `INSERT`:追加新记录 - `UPDATE`:同主键追加新版本 - `DELETE`:追加删除标记(或软删除字段更新) 读取时由索引指向“当前有效版本”。历史版本不会立即删除,而在压缩阶段统一回收。 该模型优势: - 写入路径简单,容错性好 - 崩溃恢复边界清晰 - 历史版本可追溯(压缩前) ### 1.3 热点数据逻辑 热点数据并不是单独的“缓存表”,而是由访问模式自然形成: - **读热点**:频繁命中某些主键/索引键 - **写热点**:同一主键高频更新,产生更多历史版本 系统处理方式: - 读侧:内存索引优先定位最新版本 - 写侧:持续追加,减少原地竞争 - 空间侧:通过 COMPACT 回收热点键积累的旧版本 ### 1.4 压缩逻辑与策略(COMPACT) 压缩并非“简单删文件”,而是一次受控重写过程: 1. 扫描版本链 2. 保留每个主键最新有效版本 3. 丢弃覆盖版本与删除版本 4. 重建索引映射并原子切换 触发方式: - 手动 `COMPACT ` / `COMPACT` - 自动巡检(`auto_compact_interval`)+ 阈值判断(`compact_stale_ratio`、`compact_min_stale_records`) ### 1.5 关系建模亮点:`REFERENCES ->` 与 `<-` 传统数据库通常只在 DDL 中表达“约束”;TOML-DB 进一步把“关系路径语义”也写入 DDL: - `REFERENCES users(id) -> Buyer`:定义外键约束 + 正向关系别名路径 - `Books <- books(author_id)`:定义反向一对多关系 这使 PRELOAD 能直接基于关系元数据装配对象结构,而不是每次手写 JOIN + regroup。 ### 1.6 嵌套响应格式与传统数据库差异 传统 JOIN 多为“扁平行集”(主表字段重复);TOML-DB 更强调“对象化嵌套响应”: - 主表字段在顶层 - 关联对象挂在别名 key 下 - 一对多关系天然是数组 这种格式更贴近上层 API/ORM 消费方式,能显著减少应用层二次组装。 --- ## 第二部分:使用说明 ## 1. 快速启动 ### 1.1 启动服务器 访问:[TOML-DB-RELASE](https://gitee.com/jericho-yu/toml-db-release)获取release版本(暂不支持Windows) ```bash ./toml-db ``` 首次启动时,程序会自动完成以下操作: 1. 在当前目录生成 `config.toml` 默认配置文件 2. 在数据目录下创建系统表和默认管理员账户 3. 将管理员密码写入 `/initAdminPwd` 文件 你也可以指定配置文件路径: ```bash ./toml-db -C /path/to/config.toml ``` 可选参数说明: - `-C`:配置文件路径(可选,默认 `./config.toml`) - `-D`:是否以 daemon 模式后台运行(可选,默认 `false`) 示例: ```bash # 前台模式(默认) ./toml-db # 指定配置文件 ./toml-db -C=./config.toml # daemon 后台运行 ./toml-db -D=true ``` 建议:裸启动一次程序然后输入`quit`、`Ctrl+C`退出程序。此时会生成系统默认表、启动密码和默认的配置文件,修改配置文件后再通过`-D=true|false`进行启动。 ### 1.2 获取管理员密码 管理员账户在首次启动时自动创建,用户名为 `admin`,密码随机生成。查看密码: ```bash cat ./data/initAdminPwd # 输出示例: 394eb8df4848cc713c3a91cba30af0b2 ``` > **请妥善保存此密码**,后续登录和管理操作都需要用到。 ### 1.3 配置文件说明 启动后生成的 `config.toml` 分为两个段: ```toml # ---- 网络服务配置 ---- [server] addr = ":8080" # 监听地址 session_idle_timeout = "2h" # 会话空闲超时(无操作多久断开) session_absolute_timeout = "12h" # 会话绝对超时(无论是否活跃) ping_interval = "30s" # WebSocket 心跳间隔 pong_timeout = "10s" # 等待心跳响应的超时时间 max_message_size = "1MB" # 单条消息最大大小 # ---- 存储引擎配置 ---- [storage] data_dir = "./data" # 数据存储目录 max_file_size = "100MB" # 单个数据文件最大大小(最小 100MB) auto_compact_enabled = true # 是否开启后台自动压缩 auto_compact_interval = "5m" # 压缩检测间隔 compact_stale_ratio = 0.5 # 过期数据占比阈值(超过则触发压缩) compact_min_stale_records = 1000 # 最少过期记录数门槛 ``` 修改配置后重启服务即可生效。 --- ## 2. 认证与连接 ### 2.1 登录获取会话 所有操作都需要先通过登录获取 `session_id`: ```bash curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username": "admin", "password": "你的密码"}' ``` 响应示例: ```json { "code": 0, "msg": "登录成功", "data": { "session_id": "abc123...", "user_id": "uuid-...", "username": "admin", "role": "admin" } } ``` 记下返回的 `session_id`,后续所有请求都需要携带它。 ### 2.2 使用 session_id **HTTP API 请求**:通过请求头携带: ``` Authorization: Bearer ``` **WebSocket 连接**:通过 URL 参数携带: ``` ws://localhost:8080/ws?session= ``` ---- ### 2.3 其他认证 API | 方法 | 路径 | 说明 | 认证 | |------|------|------|------| | POST | `/api/auth/register` | 注册新用户 | admin | | POST | `/api/auth/logout` | 登出(销毁会话) | Bearer | | GET | `/api/auth/profile` | 查看当前用户信息 | Bearer | #### 注册新用户(仅 admin) ```bash curl -X POST http://localhost:8080/api/auth/register \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"username": "zhangsan", "password": "123456", "role": "manager"}' ``` 角色可选值:`admin`、`manager`、`operator`。 #### 登出 ```bash curl -X POST http://localhost:8080/api/auth/logout \ -H "Authorization: Bearer " ``` #### 查看个人信息 ```bash curl http://localhost:8080/api/auth/profile \ -H "Authorization: Bearer " ``` ---- ## 3. 管理页面 服务启动后,可以通过浏览器访问内置调试页面:`http://localhost:8080/admin` 该页面提供了可视化的 SQL 执行界面,方便测试和调试查询。 ## 4. WebSocket SQL 协议 登录成功后,通过 WebSocket 连接发送 SQL 语句来操作数据。 ### 4.1 建立连接 ``` ws://host:port/ws?session= ``` 例如使用 `websocat` 工具(或任何 WebSocket 客户端): ```bash websocat "ws://localhost:8080/ws?session=你的session_id" ``` ### 4.2 发送 SQL 请求 连接建立后,发送 JSON 格式的请求消息: ```json { "id": "req-1", "sql": "SELECT * FROM products WHERE price > ? ORDER BY price DESC LIMIT 10", "argList": [9.9], "argDict": {} } ``` | 字段 | 类型 | 说明 | |------|------|------| | `id` | string | 请求唯一标识,响应中原样返回 | | `sql` | string | 要执行的 SQL 语句 | | `argList` | array | 位置参数列表(对应 `?` 占位符) | | `argDict` | object | 命名参数字典(对应 `:name` 占位符) | 请求也支持 **TOML 格式**,服务端会自动检测: ```toml id = "req-1" sql = "SELECT * FROM products" argList = [] ``` ### 4.3 接收响应 每条请求都会收到一条响应消息: ```json { "id": "req-1", "code": 0, "msg": "查询成功", "data": { ... } } ``` | 字段 | 说明 | |------|------| | `code` | `0` 表示成功,非 0 为错误码 | | `msg` | 操作结果描述 | | `data` | 查询结果或操作反馈 | ### 4.4 支持的语句类型 | 分类 | 语句 | |------|------| | 数据查询 | SELECT / JOIN / PRELOAD | | 数据写入 | INSERT / UPDATE / DELETE | | 建表与变更 | CREATE TABLE / ALTER TABLE / DROP TABLE / TRUNCATE TABLE | | 元数据 | SHOW TABLES / DESCRIBE | | 运维 | COMPACT | --- ## 5. 参数绑定 SQL 语句中的占位符支持两种参数传递方式,可以同时混用。 ### 5.1 位置参数(argList) 使用 `?` 作为占位符,按顺序传值: ```json { "id": "req-1", "sql": "SELECT * FROM products WHERE price > ? AND category = ?", "argList": [9.9, "electronics"] } ``` ### 5.2 命名参数(argDict) 使用 `:name` 作为占位符,按名称传值: ```json { "id": "req-2", "sql": "SELECT * FROM users WHERE name = :name AND role = :role", "argList": [], "argDict": { "name": "张三", "role": "admin" } } ``` ### 5.3 混合使用 两种方式可以同时使用,命名参数会被转换为位置参数追加到 `argList` 之后: ```json { "id": "req-3", "sql": "SELECT * FROM orders WHERE amount > ? AND status = :status", "argList": [100], "argDict": { "status": "shipped" } } ``` > **注意**:JOIN 的 WHERE 子句、PRELOAD WHERE/WITH 之外的所有 SQL 场景均支持上述两种参数绑定方式。PRELOAD WHERE 仅支持字面值。 --- ## 6. 建表(DDL) ### 6.1 CREATE TABLE 基本语法 ```sql CREATE TABLE 表名 ( 列名 类型 [约束...], ... ) ``` #### 支持的数据类型 | SQL 类型 | 说明 | |----------|------| | `INT8` / `INT16` / `INT32` / `INT64` | 有符号整数(8/16/32/64 位) | | `UINT8` / `UINT16` / `UINT32` / `UINT64` | 无符号整数 | | `FLOAT32` / `FLOAT64` | 浮点数 | | `STRING` | 字符串 | | `BOOL` | 布尔值 | | `TIMESTAMP` | 时间戳 | | `UUID` | UUID(通用唯一标识符) | #### 支持的约束 | 约束 | 说明 | |------|------| | `PK` | 主键(每表必须有且仅有一个) | | `AUTO` | 自增主键(仅整数类型可用) | | `NOT NULL` | 非空 | | `UNIQUE` | 唯一 | | `HASH` | 哈希索引(适合等值查询) | | `DEFAULT UUID_VN` | UUID 自动生成(N 为 1~7) | | `CREATE_TIME` | 插入时自动填充创建时间 | | `UPDATE_TIME` | 插入和更新时自动填充更新时间 | | `SOFT_DELETE` | 软删除标记(删除时标记时间戳,查询时自动过滤已删记录) | | `REFERENCES 表名(列名)` | 外键约束 | #### 示例:基本建表 ```json { "id": "create-1", "sql": "CREATE TABLE products (\n id INT64 PK AUTO,\n name STRING NOT NULL UNIQUE,\n price FLOAT64 NOT NULL,\n stock INT32,\n created_at TIMESTAMP CREATE_TIME,\n updated_at TIMESTAMP UPDATE_TIME\n)", "argList": [] } ``` #### 示例:UUID 主键 + 软删除 ```json { "id": "create-2", "sql": "CREATE TABLE orders (\n id UUID PK DEFAULT UUID_V4,\n user_id UUID NOT NULL REFERENCES users(id),\n amount FLOAT64 NOT NULL,\n status STRING,\n deleted_at INT64 SOFT_DELETE,\n created_at INT64 CREATE_TIME,\n updated_at INT64 UPDATE_TIME\n)", "argList": [] } ``` #### IF NOT EXISTS 如果表已存在则不报错: ```sql CREATE TABLE IF NOT EXISTS categories ( id INT64 PK AUTO, name STRING NOT NULL UNIQUE ) ``` ### 6.2 关系定义 TOML-DB 支持在建表时声明表之间的关系,用于后续的 JOIN 和 PRELOAD 操作。 #### 正向外键(-> 别名) 在外键约束后追加 `-> 别名`,声明 PRELOAD 加载路径名: ```sql CREATE TABLE orders ( id INT64 PK AUTO, user_id INT64 NOT NULL REFERENCES users(id) -> Buyer, product_id INT64 NOT NULL REFERENCES products(id) -> Product, amount FLOAT64 NOT NULL, status STRING ) ``` `-> Buyer` 表示 PRELOAD 时通过 `user_id` 关联加载数据,结果嵌套在 `Buyer` 键下(单个对象)。 #### 反向引用(<- 声明一对多关系) 使用 `<-` 声明当前表与子表的一对多关系(虚拟字段,不占存储空间): ```sql CREATE TABLE users ( id INT64 PK AUTO, name STRING NOT NULL, Books <- books(author_id) ) ``` `Books <- books(author_id)` 表示 `books` 表的 `author_id` 引用当前表主键。PRELOAD 时批量加载所有关联记录(数组)。 #### 完整关系定义示例 ```sql -- 作者表(含反向引用一对多) CREATE TABLE users ( id INT64 PK AUTO, name STRING NOT NULL, Books <- books(author_id) ) -- 书籍表(外键指向作者和分类) CREATE TABLE books ( id INT64 PK AUTO, title STRING NOT NULL, price FLOAT64 NOT NULL, author_id INT64 NOT NULL REFERENCES users(id) -> Author, category_id INT64 REFERENCES categories(id) -> Category ) ``` ### 6.3 查看表列表和结构 ```sql -- 列出所有表 SHOW TABLES -- 查看指定表的结构 DESCRIBE products -- 或简写 DESC products ``` ### 6.4 亮点:`REFERENCES ... ->` 与 `<-` 关系模型 这是 TOML-DB 区别于传统“仅外键约束”数据库的关键特性: - `REFERENCES users(id) -> Buyer`: 在定义外键约束的同时,声明了**关系别名路径**(`Buyer`),便于 PRELOAD 直接按路径装配对象。 - `Books <- books(author_id)`: 显式声明反向一对多关系,不依赖 ORM 约定推断。 价值: 1. 关系定义进入 DDL,模型语义更清晰 2. 查询层(PRELOAD)可直接复用关系元数据 3. 减少手写 JOIN/ON 的样板代码与错误率 4. 前端/服务层拿到的结果天然是“领域对象结构”而非生硬行集 与传统数据库相比,TOML-DB 把“约束 + 关系路径”合并到一个声明里,强调查询结果可直接用于对象装配。 --- ## 7. 表结构变更 ### 7.1 添加列 ```sql -- 基本类型与约束 ALTER TABLE products ADD COLUMN description STRING ALTER TABLE products ADD weight FLOAT64 NOT NULL -- 外键引用(引用目标表的主键列) ALTER TABLE orders ADD COLUMN user_id STRING REFERENCES users(id) -- 外键 + 正向关系别名(用于 PRELOAD 加载关联对象,参见第 13 章) ALTER TABLE orders ADD COLUMN user_id STRING REFERENCES users(id) -> Buyer -- 反向引用(通过子表的 FK 指向本表主键,PRELOAD 加载一对多关系,参见第 13 章) ALTER TABLE authors ADD COLUMN Books <- books(author_id) ``` > **关系定义不受建表时机限制**:无论是 CREATE TABLE 还是 ALTER TABLE,都可以使用 `REFERENCES ... ->` 和 `<-` 声明关系。PRELOAD 功能需要关系先被声明,但不关心声明时机。 ### 7.2 删除列 > 主键列不允许删除。删除列会触发全表数据迁移。 ```sql ALTER TABLE products DROP COLUMN description ALTER TABLE products DROP weight ``` ### 7.3 重命名列 > 重命名列会触发全表数据迁移。如果被重命名的列被其他表的外键引用,引用元数据会自动联动更新。 ```sql ALTER TABLE products RENAME COLUMN name TO title ALTER TABLE products RENAME stock TO quantity ``` ### 7.4 重命名表 > 重命名表后,所有引用该表的关系元数据(正向 `REFERENCES` 和反向 `<-`)都会自动更新。 ```sql ALTER TABLE products RENAME TO goods ``` ### 7.5 删除表和清空表 ```sql -- 删除表及其全部数据(以 _ 开头的系统表不允许删除) DROP TABLE products DROP TABLE IF EXISTS temp_data -- 不存在时不报错 -- 清空表中全部数据但保留表结构(比 DELETE 更快) TRUNCATE TABLE orders TRUNCATE orders -- 简写形式 ``` > DROP TABLE 和 TRUNCATE TABLE 都需要 `manager` 角色权限。 --- ## 8. 数据查询(SELECT) ### 8.1 基本语法 ```sql SELECT 列名 FROM 表名 [WHERE 条件] [ORDER BY 排序表达式] [GROUP BY 列名] [LIMIT 数量] [OFFSET 偏移] ``` #### 查询所有列 / 指定列 ```sql SELECT * FROM products SELECT id, name, price FROM products ``` #### 条件查询 ```sql SELECT * FROM products WHERE price > ? AND category = ? ``` #### 排序 + 分页 ```sql SELECT * FROM products ORDER BY price DESC LIMIT 10 OFFSET 20 ``` ### 8.2 WHERE 条件详解 多个条件之间用 `AND` 连接。支持以下操作符: #### 比较操作符 | 操作符 | 示例 | 说明 | |--------|------|------| | `=` | `price = ?` | 等于 | | `!=` / `<>` | `status != ?` | 不等于 | | `>` / `<` | `price > ?` | 大于 / 小于 | | `>=` / `<=` | `age >= ?` | 大于等于 / 小于等于 | 数值和字符串都支持比较。两侧能转为数值时进行数值比较,否则按字符串字典序比较。 #### 模糊匹配(LIKE) ```sql WHERE name LIKE ? -- 参数: "%coffee%" 包含 coffee -- 参数: "coffee%" 以 coffee 开头 -- 参数: "%coffee" 以 coffee 结尾 ``` LIKE 匹配 **不区分大小写**。 #### 范围查询 ```sql -- IN 列表 WHERE id IN (?) -- 参数: [1, 2, 3] 或 "a,b,c" -- NOT IN WHERE status NOT IN (?) -- BETWEEN 范围(含边界) WHERE price BETWEEN ? AND ? -- 需要两个参数 ``` #### 空值判断 ```sql WHERE deleted_at IS NULL -- 值为空 WHERE email IS NOT NULL -- 值不为空 ``` > 空值判断不需要参数占位符。 ### 8.3 ORDER BY 排序 支持基本排序、多列排序,以及 **CASE WHEN 自定义排序逻辑**: ```sql -- 基本升序/降序 SELECT * FROM products ORDER BY price ASC SELECT * FROM products ORDER BY created_at DESC -- 多列排序(按调用顺序排列优先级) SELECT * FROM products ORDER BY category ASC, price DESC -- CASE WHEN 自定义排序 -- 按角色优先级排序 SELECT * FROM users ORDER BY CASE role WHEN 'admin' THEN 1 WHEN 'manager' THEN 2 ELSE 3 END -- 置顶缺货商品,其余按价格降序 SELECT * FROM products ORDER BY CASE WHEN stock = 0 THEN 1 ELSE 0 END, price DESC ``` ### 8.4 GROUP BY 与分页 ```sql -- 分组 SELECT * FROM orders GROUP BY buyer_id -- 手动分页(LIMIT + OFFSET) SELECT * FROM products LIMIT 10 OFFSET 20 ``` ### 8.5 查询响应结构 **多条记录**: ```json { "total": 100, "page": 0, "size": 0, "max_page": 0, "data": [...] } ``` **单条记录**(LIMIT 1): ```json { "total": 1, "page": 1, "size": 1, "max_page": 1, "data": {...} } ``` --- ## 9. 数据写入(INSERT) ### 9.1 基本语法 ```sql INSERT INTO 表名 (列1, 列2, ...) VALUES (?, ?, ...) ``` 以下标签声明的字段 **无需手动指定**,服务端会自动注入: | 标签 | 自动行为 | |------|---------| | `PK AUTO` | 自增主键 ID | | `DEFAULT UUID_VN` | UUID 自动生成 | | `CREATE_TIME` | 创建时间戳 | | `UPDATE_TIME` | 更新时间戳 | ### 9.2 插入示例 ```json { "id": "insert-1", "sql": "INSERT INTO products (name, price, stock) VALUES (?, ?, ?)", "argList": ["Coffee", 9.9, 100] } ``` 响应中返回包含自动生成字段的完整记录: ```json { "id": "insert-1", "code": 0, "msg": "插入成功", "data": { "datum": { "id": "1", "name": "Coffee", "price": 9.9, "stock": 100, "created_at": "2026-04-13T12:00:00Z", "updated_at": "2026-04-13T12:00:00Z" } } } ``` ### 9.3 全自动插入 如果表的所有业务字段都有默认值或自动标签,可以不传任何字段: ```sql INSERT INTO logs () VALUES () ``` --- ## 10. 数据更新(UPDATE) ### 10.1 基本语法 ```sql UPDATE 表名 SET 列1 = ?, 列2 = ? WHERE 主键列 = ? ``` **重要约束**:WHERE 中 **必须包含主键条件**。 ### 10.2 行为说明 - `UPDATE_TIME` 字段自动更新为当前时间 - `CREATE_TIME` 字段受保护,不可修改 - 由于 Append-Only 存储模型,UPDATE 内部流程是:读取旧记录 → 合并 SET 值 → 写入完整新记录 ### 10.3 更新示例 ```json { "id": "update-1", "sql": "UPDATE products SET price = ?, stock = ? WHERE id = ?", "argList": [19.9, 50, "1"] } ``` 响应返回合并后的完整记录。 --- ## 11. 数据删除(DELETE) ### 11.1 基本语法 ```sql DELETE FROM 表名 WHERE 主键列 = ? ``` **重要约束**:WHERE 中 **必须包含主键条件**。 ### 11.2 删除行为 - 如果表定义了 `SOFT_DELETE` 字段 → 执行 **软删除**(标记删除时间),数据保留在存储中,后续 SELECT 自动过滤 - 否则执行 **物理删除** ### 11.3 删除示例 ```json { "id": "delete-1", "sql": "DELETE FROM products WHERE id = ?", "argList": ["1"] } ``` --- ## 12. JOIN 多表连接**(重点)** TOML-DB 完整支持 5 种标准 JOIN 类型。与传统 SQL 不同,**JOIN 结果采用嵌套 JSON 结构** —— 主表字段在顶层,JOIN 表数据嵌套在以别名(或表名)为 key 的子对象中。 ### 12.1 语法 ```sql SELECT 列名 FROM 主表 [别名] [JOIN类型] 连接表 [别名] ON 连接条件 [JOIN类型] 连接表 [别名] ON 连接条件 [WHERE 条件] [ORDER BY 排序] [LIMIT 数量] [OFFSET 偏移] ``` ### 12.2 JOIN 类型 | 语法 | 说明 | |------|------| | `JOIN` / `INNER JOIN` | 内连接:仅返回两表均匹配的行 | | `LEFT JOIN` / `LEFT OUTER JOIN` | 左外连接:左表全保留 + 右表匹配(无匹配时为 null) | | `RIGHT JOIN` / `RIGHT OUTER JOIN` | 右外连接:右表全保留 + 左表匹配 | | `FULL OUTER JOIN` / `FULL JOIN` | 全外连接:两表全部(无匹配的一侧为 null) | | `CROSS JOIN` | 交叉连接:笛卡尔积,无需 ON 条件 | ### 12.3 表别名 ```sql -- 空格分隔 SELECT * FROM orders o LEFT JOIN users u ON u.id = o.user_id -- AS 关键字 SELECT * FROM orders AS o LEFT JOIN users AS u ON u.id = o.user_id ``` ### 12.4 ON 条件 ON 条件使用 `表.列 = 表.列` 格式,多个条件用 `AND` 连接。非 CROSS JOIN 必须指定 ON 条件: ```sql ON u.id = o.user_id ON u.id = o.user_id AND u.tenant_id = o.tenant_id ``` ### 12.5 结果结构示例 **LEFT JOIN 查询**: ```json { "id": "req-1", "sql": "SELECT * FROM orders o LEFT JOIN users u ON u.id = o.user_id", "argList": [] } ``` 响应(嵌套 JSON): ```json { "data": { "total": 3, "data": [ { "id": 1, "user_id": 10, "amount": 199.0, "status": "shipped", "u": { "id": 10, "name": "Alice", "email": "alice@example.com" } }, { "id": 2, "user_id": 20, "amount": 350.0, "status": "pending", "u": { "id": 20, "name": "Bob", "email": "bob@example.com" } }, { "id": 3, "user_id": 999, "amount": 50.0, "status": "cancelled", "u": null } ] } } ``` 关键特征: - 主表字段放在顶层 - JOIN 表数据嵌套在 **别名**(此处为 `u`)key 下 - 无匹配时(LEFT JOIN),嵌套值为 `null` - 多表 JOIN 时,每个连接表各自一个嵌套 key ### 12.6 带 WHERE 的 JOIN WHERE 中可以通过 `表.列` 限定字段所属表: ```json { "id": "req-2", "sql": "SELECT * FROM orders o INNER JOIN users u ON u.id = o.user_id WHERE o.amount > ? AND u.name = ?", "argList": [100, "Alice"] } ``` ### 11.7 多表 JOIN ```sql SELECT * FROM orders o LEFT JOIN users u ON u.id = o.user_id INNER JOIN products p ON p.id = o.product_id WHERE o.amount > ? ``` 响应包含两层嵌套: ```json { "id": 1, "user_id": 10, "product_id": 5, "amount": 199.0, "u": { "id": 10, "name": "Alice" }, "p": { "id": 5, "name": "键盘", "price": 199.0 } } ``` ### 12.8 CROSS JOIN 笛卡尔积 ```sql SELECT * FROM colors CROSS JOIN sizes ``` 无需 ON 条件,返回两表的笛卡尔积。 ### 12.9 注意事项 - JOIN 使用 **哈希索引匹配** 实现,适合等值连接场景 - JOIN 涉及的所有表都需要当前用户具有查询权限 - 嵌套 key **优先使用别名**,无别名时使用表名 - 多个 JOIN 按书写顺序依次执行,后续 JOIN 的 ON 条件可以引用前面 JOIN 的表 - JOIN 查询的 WHERE 子句 **完全支持位置参数和命名参数** ### 12.10 与传统数据库响应格式差异(嵌套响应) 传统 SQL 数据库的 JOIN 结果通常是“扁平行集”(每行重复主表字段);TOML-DB 默认返回**嵌套结构**: - 主表字段保留在顶层 - 关联表以别名(或表名)作为子对象键 - 一对多关系可直接返回数组 传统扁平行示意: ```text order_id | amount | user_id | user_name 1 | 99.0 | 10 | Alice 2 | 50.0 | 10 | Alice ``` TOML-DB 嵌套行示意: ```json { "id": 1, "amount": 99.0, "u": { "id": 10, "name": "Alice" } } ``` 这种结构更贴近 API 输出与对象模型,减少应用层二次 regroup/merge 的处理成本。 --- ## 13. PRELOAD 关系预加载 PRELOAD 允许在一次查询中自动加载关联数据,无需手动编写 JOIN 条件。通过 DDL 中定义的关系元数据,PRELOAD 自动完成关联匹配和数据装配,有效避免 N+1 查询问题。 > **前置条件**:使用 PRELOAD 前,必须在 CREATE TABLE 中通过 `-> 别名` 或 `<-` 声明好关系(参见 [6.2 关系定义](#6.2-关系定义))。 ### 13.1 与 JOIN 的区别 | 对比项 | JOIN | PRELOAD | |--------|------|---------| | 连接条件 | 手动指定 ON 条件 | 基于 DDL 定义的关系自动关联 | | 结果结构 | 嵌套对象(别名为 key) | 嵌套对象或数组(路径名为 key) | | 适用场景 | 灵活的多表关联 | 实体关系的自动加载 | ### 13.2 语法 ```sql SELECT 列名 FROM 表名 PRELOAD { (路径), (路径 WHERE 条件), (路径 WHERE 条件 WITH 计数条件), (*) } [WHERE 主查询条件] [ORDER BY 排序] [LIMIT 数量] ``` ### 13.3 路径语法 | 路径形式 | 说明 | 示例 | |----------|------|------| | `(Name)` | 单层关系 | `(Author)` — 加载关联作者 | | `(A.B)` | 多层级嵌套路径(最深 5 层) | `(Author.Books)` — 加载作者及其书籍 | | `(*)` | 通配符:展开所有直接关系 | `(*)` — 加载所有正向 FK 和反向引用 | ### 13.4 正向外键预加载(多对一 → 单个对象) 假设建表时定义了: ```sql CREATE TABLE books ( id INT64 PK AUTO, title STRING NOT NULL, price FLOAT64, author_id INT64 REFERENCES authors(id) -> Author ) ``` 查询书籍并加载作者: ```json { "id": "preload-1", "sql": "SELECT * FROM books PRELOAD { (Author) }", "argList": [] } ``` 响应: ```json { "data": { "total": 3, "data": [ { "id": 1, "title": "Go 编程实战", "price": 89.0, "author_id": 42, "Author": { "id": 42, "name": "张三" } }, { "id": 2, "title": "Go 并发模式", "price": 75.0, "author_id": 42, "Author": { "id": 42, "name": "张三" } } ] } } ``` 正向外键的结果为 **单个嵌套对象**(或 null)。 ### 13.5 反向引用预加载(一对多 → 数组) 假设建表时定义了: ```sql CREATE TABLE authors ( id INT64 PK AUTO, name STRING NOT NULL, Books <- books(author_id) ) ``` 查询作者并加载其所有书籍: ```json { "id": "preload-2", "sql": "SELECT * FROM authors PRELOAD { (Books) }", "argList": [] } ``` 响应: ```json { "data": { "total": 2, "data": [ { "id": 42, "name": "张三", "Books": [ { "id": 1, "title": "Go 编程实战", "price": 89.0, "author_id": 42 }, { "id": 2, "title": "Go 并发模式", "price": 75.0, "author_id": 42 } ] }, { "id": 43, "name": "李四", "Books": [ { "id": 3, "title": "Rust 入门", "price": 99.0, "author_id": 43 } ] } ] } } ``` 反向引用的结果为 **嵌套数组**(可能为空数组 `[]`)。 ### 13.6 通配符预加载 一次加载当前表的所有直接关系(一层,不递归): ```sql SELECT * FROM books PRELOAD { (*) } ``` 等价于显式列出所有直接关系: ```sql SELECT * FROM books PRELOAD { (Author), (Category) } ``` ### 13.7 PRELOAD WHERE — 行级过滤 对关联数据进行过滤。使用 **字面值**(不支持 `?` 占位符): ```sql SELECT * FROM authors PRELOAD { (Books WHERE price > 50 AND status = 'published') } ``` 规则: - **正向外键**:不满足条件的行,嵌套值置为 `null` - **反向引用**:仅返回满足条件的子记录,不影响主表行的保留 ### 13.8 PRELOAD WITH — 基于子记录数量过滤主表 语法:`count([内部条件]) 比较符 数值` | WITH 表达式 | 含义 | |------------|------| | `count() > 0` | 至少有 1 条子记录 | | `count() = 0` | 没有子记录 | | `count(price > 50) >= 3` | 价格大于 50 的子记录至少 3 条 | 示例:查询至少出版了 2 本高价书的作者 ```sql SELECT * FROM authors PRELOAD { (Books WITH count(price > 100) >= 2) } ``` ### 13.9 WHERE + WITH 组合 先过滤子记录,再按过滤后的数量过滤主记录: ```sql SELECT * FROM authors PRELOAD { (Books WHERE status = 'published' WITH count(price > 50) > 0) } WHERE name LIKE ? -- argList: ["%张%"] ``` ### 13.10 多层级路径 查询书籍并加载作者信息以及作者的其他书籍: ```json { "id": "preload-5", "sql": "SELECT * FROM books PRELOAD { (Author.Books) } WHERE id = ? LIMIT 1", "argList": [1] } ``` 响应: ```json { "data": { "id": 1, "title": "Go 编程实战", "price": 89.0, "author_id": 42, "Author": { "id": 42, "name": "张三", "Books": [ { "id": 1, "title": "Go 编程实战", "price": 89.0, "author_id": 42 }, { "id": 2, "title": "Go 并发模式", "price": 75.0, "author_id": 42 } ] } } } ``` ### 13.11 注意事项 - 使用 PRELOAD 前必须在 CREATE TABLE 中通过 `-> 别名` 或 `<-` 声明关系 - 路径最大深度 **5 层** - 正向外键内部通过 LEFT JOIN 实现,反向引用通过批量分组加载实现 - PRELOAD WHERE 使用字面值,不支持 `?` 占位符 - 通配符 `(*)` 只展开直接关系,不递归 - 多个 PRELOAD 项可同时加载:`PRELOAD { (Author), (Category), (Tags) }` --- ## 14. 子查询 子查询允许在 WHERE 条件中嵌入另一个 SELECT 查询。子查询在执行前被 **预解析** 为具体值——IN 子查询转换为值列表,标量子查询转换为单个值,EXISTS 转换为布尔判断——然后主查询使用这些具体值进行过滤。 ### 14.1 三种子查询形式 | 形式 | 语法 | 说明 | |------|------|------| | **IN / NOT IN** | `field IN (SELECT col FROM ...)` | 字段值是否在子查询结果集中 | | **标量比较** | `field > (SELECT col FROM ... LIMIT 1)` | 与子查询的单个标量值比较 | | **EXISTS / NOT EXISTS** | `EXISTS (SELECT 1 FROM ...)` | 子查询是否返回至少一行 | ### 14.2 参数绑定 子查询中的 `?` 占位符从外层 `argList` 中 **按出现顺序依次消耗**: ```json { "sql": "SELECT * FROM users WHERE age > ? AND id IN (SELECT user_id FROM orders WHERE amount > ?) AND status = ?", "argList": [18, 100, "active"] } ``` 参数分配: - 第 1 个 `?`(18)→ `age > 18` - 第 2 个 `?`(100)→ 子查询中 `amount > 100` - 第 3 个 `?`(`active`)→ `status = 'active'` ### 14.3 IN / NOT IN 子查询 查询所有下过订单金额超过 100 的用户: ```json { "id": "sub-1", "sql": "SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > ?)", "argList": [100] } ``` 内部执行过程: 1. 先执行子查询 `SELECT user_id FROM orders WHERE amount > 100`,得到 `[10, 20, 42]` 2. 主查询变为 `SELECT * FROM users WHERE id IN [10, 20, 42]` ### 14.4 标量子查询 查询价格高于最低价商品的商品: ```json { "id": "sub-3", "sql": "SELECT * FROM products WHERE price > (SELECT price FROM products ORDER BY price LIMIT 1)", "argList": [] } ``` 标量子查询取结果的 **第一行第一列** 作为比较值。建议搭配 `LIMIT 1`。 ### 14.5 EXISTS / NOT EXISTS 查询至少有一个订单的用户: ```json { "id": "sub-4", "sql": "SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE user_id = ?)", "argList": [42] } ``` ### 14.6 嵌套限制 子查询支持嵌套,最大深度为 **3 层**: ```sql -- 2 层嵌套(允许) WHERE id IN ( SELECT user_id FROM orders WHERE product_id IN (SELECT id FROM products WHERE category = ?) ) ``` ### 14.7 PRELOAD 中的子查询 PRELOAD WHERE 和 WITH 表达式中也可以包含子查询,同样会在执行前被预解析为字面值: ```sql SELECT * FROM authors PRELOAD { (Books WHERE category_id IN (SELECT id FROM categories WHERE name = 'fiction')) } ``` > 注意:PRELOAD 子查询中不支持 `?` 占位符(因 PRELOAD WHERE 本身使用字面值语法)。 --- ## 15. 权限系统与用户管理 ### 15.1 角色体系 | 角色 | 说明 | 权限范围 | |------|------|---------| | **admin** | 管理员 | 所有权限,可管理用户和授权 | | **manager** | 管理者 | 已授权表的增删改查 + 全部 DDL | | **operator** | 操作者 | 已授权表的查询 + COMPACT/MIGRATE | ### 15.2 权限矩阵 | 操作 | admin | manager | operator | |------|-------|---------|----------| | SELECT 已授权表 | 全部 | ✅ | ✅ | | INSERT / UPDATE / DELETE | 全部 | ✅(已授权表) | ❌ | | CREATE / ALTER / DROP / TRUNCATE | 全部 | ✅ | ❌ | | COMPACT | 全部 | ❌ | ✅(已授权表) | | SHOW TABLES / DESCRIBE | 全部 | ✅ | ✅ | | 用户管理与授权 | ✅ | ❌ | ❌ | ### 15.3 授权管理(仅 admin) #### 授予权限 ```bash curl -X POST http://localhost:8080/api/admin/privilege \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "user_id": "目标用户的UUID", "table_name": "products", "privilege": "manager" }' ``` `privilege` 可选值: - `manager`:该用户可对指定表执行 query/insert/update/delete - `operator`:该用户可对指定表执行 query/compact/migrate #### 撤销权限 ```bash curl -X DELETE http://localhost:8080/api/admin/privilege \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "user_id": "目标用户的UUID", "table_name": "products", "privilege": "manager" }' ``` ### 15.4 系统表保护 以 `_` 开头的系统表(如 `_sys_user`、`_sys_user_privilege`)禁止非 admin 直接操作。即使 manager 角色也无法访问系统表。 --- ## 16. 存储原理与运维 ### 16.1 Append-Only 存储模型 TOML-DB 采用 **Append-Only(追加写入)** 存储引擎,这是它最核心的设计特点。与传统数据库“原地覆盖页”不同,TOML-DB 的写路径是: 1. **INSERT**:直接追加一条新记录 2. **UPDATE**:按同主键追加“新版本记录” 3. **DELETE**:追加删除标记(或软删除字段更新) 典型记录演进如下: ``` data_0.db: [记录1] [记录1-更新版] [记录2] [记录1-删除标记] [记录3] ↑ ↑ ↑ 同ID新版本覆盖 新插入 软删除(追加标记) ``` **核心规则**:数据一旦落盘,原则上不做原地修改;读取时总是按“主键索引指向的最新版本”返回。 这使得系统具备天然的“写放大换稳定性”特征: - 写入路径简单、故障恢复容易 - 数据一致性依赖“追加成功 + 索引切换” - 历史版本在压缩前可追溯 **优势**: - **天然容错**:写入中途断电不会损坏已有数据 - **原子性**:追加操作要么完全成功,要么完全不生效 - **版本追溯**:旧版本数据在被清理前一直可读 **代价**:需要定期执行 **COMPACT(压缩)** 来清理过期数据,释放磁盘空间。 ### 16.2 自动分片 单表数据文件达到上限(默认 100MB)时自动拆分为多个文件: ``` ./data/products/ ├── data_0.db # 第一块(0~100MB) ├── data_1.db # 第二块(100~200MB) └── data_2.db # 第三块(200~300MB,以此类推) ``` 对上层查询而言,分片是透明的。 ### 16.3 热点数据逻辑(Hot Data) 在 Append-Only 模型下,“热数据”本质是: - 更新频繁、版本迭代快的主键记录 - 高频查询命中的索引键(PK / HASH / INDEX) TOML-DB 对热点数据的处理逻辑可概括为: 1. **读路径热点**:优先走内存索引定位到最新版本,避免全表扫描 2. **写路径热点**:持续追加新版本,不做页级锁竞争与原地改写 3. **空间热点副作用**:热点记录会产生更多历史版本,增加“过期数据比例” 4. **治理手段**:依赖 COMPACT 定期回收历史版本,降低无效 I/O 因此,热点访问不是通过复杂缓存层实现,而是通过“内存索引 + 追加写 + 压缩回收”形成闭环。 ### 16.4 索引与缓存 - **主键索引**:表打开时自动加载到内存 - **普通索引**(`INDEX` / `HASH`):同样常驻内存 - 查询时优先走内存索引,避免全表扫描 ### 16.5 数据目录结构 ``` ./data/ ├── initAdminPwd # 初始化的管理员密码 ├── _sys_user/ # 系统表:用户 │ ├── data_0.db │ └── schema.json ├── _sys_user_privilege/ # 系统表:权限 │ ├── data_0.db │ └── schema.json └── YourTableName/ # 业务表 ├── data_0.db # 第一块数据文件 ├── data_1.db # 后续滚动生成的文件 └── schema.json # 表结构定义(DDL 创建的表才有) ``` ### 16.6 COMPACT 压缩 COMPACT 用于清理旧版本记录和已软删除的数据,释放磁盘空间。 压缩核心策略: 1. **按主键只保留最新有效版本** 2. **丢弃已删除或已被覆盖的历史记录** 3. **重建压缩后数据文件与索引映射** 4. **原子切换新旧文件**(避免中途失败导致读写异常) 触发策略分两类: - **手动触发**:适合大批量导入/更新后立即回收空间 - **自动触发**:按时间间隔巡检,结合“过期比例 + 最小过期数”阈值决定是否执行 建议经验: - 更新频繁(热点明显)的业务表:提高压缩频率 - 冷数据表:延长压缩间隔,减少无意义重写 - 观察 `compact_stale_ratio` 与实际磁盘增长,动态调参 #### 手动压缩 ```json { "id": "compact-1", "sql": "COMPACT products", "argList": [] } ``` 或压缩全部表: ```json { "id": "compact-all", "sql": "COMPACT", "argList": [] } ``` #### 自动压缩 通过 `config.toml` 配置后台自动压缩: ```toml [storage] auto_compact_enabled = true # 开关 auto_compact_interval = "5m" # 检测间隔 compact_stale_ratio = 0.5 # 过期数据占比超过 50% 时触发 compact_min_stale_records = 1000 # 至少 1000 条过期记录才触发(避免小表频繁压缩) ``` #### 压缩响应 返回压缩统计信息(清理的记录数等)。 --- ### 16.7 与传统存储引擎的差异总结 | 维度 | 传统页式引擎(常见) | TOML-DB(Append-Only) | |------|----------------------|-------------------------| | 更新方式 | 原地修改页 | 追加新版本 | | 删除方式 | 页内标记/回收 | 追加删除标记,压缩时清理 | | 崩溃恢复 | 依赖 WAL/Checkpoint | 依赖追加完整性与索引恢复 | | 热点冲突 | 可能产生页锁竞争 | 以追加为主,冲突模型更简单 | | 空间回收 | 后台 vacuum/整理 | COMPACT 主动/自动回收 | --- ## 17. 启动模式、日志与终端 SQL 控制台 ### 17.1 前台 / daemon 启动模式 - 前台模式(默认):`-D=false`,输出同时写控制台和日志文件。 - daemon 模式:`-D=true`,进程后台运行,日志仅写文件。 ```bash ./toml-db -D=false ./toml-db -D=true ``` ### 17.2 日志配置与轮转 配置文件中的 `[log]` 段用于控制日志目录与滚动策略: ```toml [log] dir = "./logs" max_size = "100MB" max_files = 10 ``` 说明: - `dir`:日志目录 - `max_size`:单个日志文件最大大小,超限后自动滚动 - `max_files`:最多保留日志文件数(含当前),超出自动删除最旧文件 默认日志文件为:`./logs/server.log`。 ### 17.3 终端 SQL 控制台(前台模式) 服务启动后,前台终端会自动进入 SQL 控制台(daemon 模式不启用)。 特性: - 输入 SQL,使用 `;` 结尾执行 - 支持多行 SQL 输入 - 支持 `↑ / ↓` 浏览历史记录 - `Ctrl+C` 清空当前输入(不退出进程) - `exit` 或 `quit` 然后按 `Ctrl+C` 退出控制台输入循环 - 终端执行结果输出为 **TOML**(仅 webServer 对外协议支持 TOML/JSON 转换) 示例: ```sql SHOW TABLES; DESCRIBE users; SELECT * FROM users LIMIT 5; ``` --- ## 附录 B:发布说明 `toml-db` 默认以闭源可执行文件形态交付,推荐将 `toml-db-kernal/cmd` 目录下的 `Makefile` 作为统一构建入口。该 `Makefile` 已内置 `release`、`debug`、`upx`、`size` 与 `clean` 五个目标,便于在发布、排障与体积优化之间切换。 ### B.1 构建目标 在 `toml-db-kernal/cmd` 目录执行: ```bash make release make debug make upx make size make clean ``` 含义如下: - `make release`:生成发布版二进制 `toml-db` - 使用 `-trimpath` - 使用 `-ldflags="-s -w -buildid="` 去除调试信息并缩小体积 - `make debug`:生成调试版二进制 `toml-db-debug` - 使用 `-tags debug` - 保留更完整的调试能力,便于排查问题 - `make upx`:先构建 `release`,再对 `toml-db` 执行 `upx --best --lzma` 压缩 - `make size`:查看当前 `toml-db` / `toml-db-debug` 产物体积 - `make clean`:删除构建产物 ### B.2 release / debug 的差异 当前构建已通过 Go build tag 将 `pprof` 行为拆分: - `debug` 构建(`//go:build debug`) - 自动启动本地 `pprof` - 监听地址:`127.0.0.1:6060` - 适合开发调试、性能采样、压测分析 - `release` 构建(`//go:build !debug`) - 不启动 `pprof` - 适合正式交付,体积更小、暴露面更低 因此,推荐策略是: - 对外发布默认提供 `toml-db` - 内部排障或性能测试时使用 `toml-db-debug` - 如果需要进一步缩小体积,可对 `toml-db` 执行 `make upx` ### B.3 推荐发布产物 建议对用户交付以下内容: 1. `toml-db`(release 产物) 2. `config.toml` 示例配置 3. 本 README 文档 4. (可选)官方客户端或协议说明,如 `toml-db-driver` 其中: - `toml-db`:默认正式发布版本 - `toml-db-debug`:仅建议内部保留,不建议直接提供给最终用户 - `UPX` 压缩后的 `toml-db`:适合对包体积敏感的场景,但建议在目标平台完成启动验证后再发布 ### B.4 README 同步策略 `toml-db-kernal/readme.md` 作为主文档;`toml-db-release/README.md` 必须保持与其同步。后续凡是发布方式、构建命令、调试能力、交付物说明发生变更,均应以本文件为准,并整段镜像同步到 `toml-db-release/README.md`。