修bug,完善功能
build / build (push) Blocked by required conditions
tests / build (push) Waiting to run
tests / tests (push) Waiting to run

This commit is contained in:
ChuNa7a committed 2026-10-16 23:52:12 +08:00
1 parent 30364b1c4b
commit 6996e19694
125 files changed
+15343 -3746

No files matched your search

+194 -40
View File
@@ -2,10 +2,35 @@
[← 返回 README](../README.md)
**本页导航:** [组件与访问路径](#组件与访问路径) · [数据目录](#数据目录) · [会话与并发](#会话与并发) · [SQL 查询处理](#sql-查询处理) · [快照事务](#快照事务) · [WAL 与崩溃恢复](#wal-与崩溃恢复) · [页面与空间回收](#页面与空间回收) · [身份、授权与系统目录](#身份授权与系统目录) · [权限变更与会话失效](#权限变更与会话失效) · [扩展方向](#扩展方向)
本页内容:
- [组件与访问路径](#组件与访问路径)
- [数据目录](#数据目录)
- [会话与并发](#会话与并发)
- [SQL 查询处理](#sql-查询处理)
- [快照事务](#快照事务)
- [WAL 与崩溃恢复](#wal-与崩溃恢复)
- [页面与空间回收](#页面与空间回收)
- [身份、授权与系统目录](#身份授权与系统目录)
- [权限变更与会话失效](#权限变更与会话失效)
- [扩展方向](#扩展方向)
本页面向需要理解或修改 ChuSQL 的开发者,说明组件职责、查询执行过程及存储边界。SQL 的使用方法见 [SQL 语法](syntax.md)。
ChuSQL 采用客户端 / 服务端架构。正常运行时,只有数据库服务直接访问数据文件;离线初始化与恢复由 bootstrap 完成。
阅读实现细节前,先了解本文使用的术语:
| 术语 | 含义 |
| --- | --- |
| schema(表结构) | 列名、类型和约束等定义;语义检查与优化以此为依据 |
| catalog(系统目录) | 记录数据库对象及其结构的元数据;判断表是否存在以此为准 |
| 绑定与槽位 | 绑定将列引用对应到输入行的位置,这个位置称为槽位 |
| 结构守卫 | 在执行前和存储操作锁内复查依赖,拒绝使用已经失效的表结构 |
| DOMAIN(领域类型) | 为基础类型命名,并可附加默认值和约束的自定义类型 |
| WAL / LSN | WAL 是恢复用的预写日志,LSN 是标识日志进度的递增序号 |
| DML / DDL | DML 包括插入、更新和删除数据;DDL 包括创建、修改和删除结构 |
## 组件与访问路径
```mermaid
@@ -27,9 +52,15 @@ flowchart LR
| chusql-core/storage | catalog、页面、B+ 树、缓存、WAL 与持久化 |
| csql-bootstrap | 安装引导及离线系统目录修复、恢复、重置 |
查询 AST 的 `Select` 使用 `selectItems :: [(String, Expr)]` 保存输出名称与投影表达式,`selectHaving` 保存分组条件;`OrderKey` 明确区分名称、输出序号和表达式。普通列使用 `Col`,别名、计算和聚合使用相同结构。便捷函数 `makeSelect` 接受列名列表并构造这些投影项。
服务默认监听 127.0.0.1:7777,Web 默认监听 127.0.0.1:7778。配置位置和平台数据目录见[配置选项](config.md)。
服务默认监听 127.0.0.1:7777,Web 默认监听 127.0.0.1:7778。配置位置和平台数据目录见 [config.md](config.md)。
### 存储名称校验
存储层解析库限定名后,物理表名、声明列名和写入行顶层字段名使用同一规则:名称非空,只含 ASCII 字母、数字和下划线。SQL 标识符另遵守解析器规则。
单行、批量、整表替换、结构替换及事务载荷在处理请求时拒绝非法字段名,不修改数据和 WAL。校验不遍历文本内容或复合值内部的键。
旧数据不自动改名,原始扫描与历史 WAL 重放保留兼容;后续重写需显式提供合法字段名。
## 数据目录
@@ -46,7 +77,7 @@ flowchart LR
## 会话与并发
- 每条 TCP 连接拥有独立会话,当前数据库和事务互不影响。
- 读请求可并行,写请求由存储层协调。
- 服务端业务请求共享全局权限门,数据写语句和提交独占目标数据库的执行门,不同数据库可并行写;权限、DDL 和管理员存储请求独占全局门。库门按名称排序获取并在取消时释放,避免跨库等待环。存储层仍维护每库读写门及表锁。
- Web 与 CLI 共享服务端的权限和执行规则,不直接读写数据目录。
- Web 登录会话分别持有普通 API / SQL 连接与表格编辑连接,编辑事务按页面标识固定数据库。表格读取携带编辑标识时使用该事务快照;普通查询不进入编辑事务。
- 同一 Web Cookie 的请求串行执行。编辑提交以固定保存点重放本地编辑集,失败整体回退;成功回执按提交标识去重。SQL 控制台事务另按页面与标签标识限制连接访问。
@@ -55,21 +86,104 @@ flowchart LR
查询依次经过解析、基于 schema 的语义检查、优化和执行。库名、表名、列名及别名在语义层解析;存储层使用精确的表名键。
绑定查询保存依赖对象编号、schema 版本、列定义和传递 DOMAIN 依赖。执行前复查,真实存储的每次读取还在相关库读锁内验证全部依赖;结构改变或对象替换明确报错。守卫不提供整条多请求查询的数据快照;旧目录的未知身份/版本保留兼容校验,旧存储未确认守卫则拒绝执行。
### 解析后的查询表示
查询由 `Runtime.Query.bindQuery` 统一准备并绑定,输出按位置校验,保留重复列名。优化完成后将表达式、排序及分组键绑定到输入布局;物理列顺序不同会在存储边界整理,缺列或位置不符明确报错。`ORDER BY` 输出序号保留到计划生成,选择对应投影表达式或 DISTINCT 输出槽位,不通过别名再次查找;非排序投影仍在 LIMIT 后求值。
解析器将 SQL 转为抽象语法树(AST)。其中 `Select` 的字段与辅助函数分工如下:
聚合计划合并投影、HAVING 和排序表达式中的当前层聚合,重复调用只计算一次,内部列使用 SQL 标识符无法声明的名称。HAVING 强制形成分组(无键时为整体组),过滤位于 Aggregate 之后、去重与排序截断之前。表达式排序先计算隐藏排序键并保留投影所需列,再排序、截断和投影;DISTINCT 排序映射到已有输出槽位。HAVING 和排序子查询同样参与结构依赖守卫与 SELECT 权限检查。
| 名称 | 用途 |
| --- | --- |
| `selectItems :: [(String, Expr)]` | 保存输出名称与投影表达式,即查询要返回的列或计算结果 |
| `selectHaving` | 保存分组条件 |
| `OrderKey` | 区分按名称、输出序号和表达式排序 |
| `Col` | 表示普通列;别名、计算和聚合使用相同的投影项结构 |
| `makeSelect` | 接受列名列表并构造投影项 |
相关子查询按最近到最远的独立列作用域查找,最近层歧义不向外回退;二段限定引用只在最近声明该来源的层解析,裸列可继续向外查找。`BoundSubquery` 保存父布局中的取值表达式,缓存按槽位和值区分;重绑定保留局部和外层身份,原位置失效时只允许唯一同名列重定位。执行期外层行仍扁平化,尚无独立作用域或来源编号,结构改写须在绑定前完成。
### 类型能力与复合值运算
写语句按目标 schema 绑定输入表达式和赋值目标。INSERT 拒绝重复目标列,按输入顺序求值,再按 schema 顺序补默认值、NULL 和自增值;UPDATE 的重复赋值读取前一次修改结果,全部赋值后检查 CHECK,再检查最终行集的 NOT NULL、UNIQUE 和 PRIMARY KEY。唯一性检查保留整数与整值浮点的一致语义,多个 NULL 不冲突;检查通过前不发布修改。CHECK、非空和唯一性按列槽位取值,INSERT 已有行及引擎 ALTER 输入行按原 schema 整理,缺列不跳过。
运行时类型描述保存相等比较(Eq)、排序(Ord)和哈希(Hash)能力,复合类型按参数递归派生。语义检查按运算要求能力,DOMAIN 沿用基础类型。值排序和哈希按结构执行,不使用显示文本;嵌套浮点正负零保持相等与哈希一致。
INSERT、UPDATE、DELETE 和已有表的结构变更由 `Engine.bindWrite` 生成不保存数据行的 `BoundWrite`,`executeWriteM` 在执行前复查结构,允许跨数据及统计变化复用。DML 在绑定阶段固定 INSERT 输入槽位、UPDATE 目标槽位、DELETE 行布局和 CHECK / NOT NULL / UNIQUE / PRIMARY KEY 约束;复用时不重新解析或绑定这些内容,自增起点与行集约束仍取当前数据。结构变更则在执行时对变更后的 schema 绑定并检查约束。计划携带目标表、嵌套查询及传递 DOMAIN 依赖;ADD COLUMN / ALTER TYPE 还包含新声明类型的 DOMAIN。建表、建删库、DOMAIN 管理和会话命令继续走各自入口,不属于已有表写计划。
值与类型编码保持版本 1,能力由代码中的类型描述提供,不写入已有值载荷。索引键编码是独立约束,运行时复合键不进入索引探测。SQL 行为见 [复合值的比较、排序与分组](types.md#比较排序与分组)。
写入前先发送只读的 `validate_write` 请求,确认存储支持写守卫并校验依赖;未确认时不发送实际修改请求。实际读取在相关库共享门内复查,实际写入在目标库独占门及其他依赖库共享门内复查,跨库按库名排序取门。对象替换或结构版本变化使旧绑定失效,包括结构改变后恢复原定义。守卫保证结构校验与单次存储操作之间不发生 DDL,不提供整条写语句的数据快照或数据冲突检测。
### 绑定查询与结构复查
CHECK 定义和执行共用类型、函数及列槽位绑定入口,拒绝聚合、子查询和非布尔条件。DROP COLUMN 校验保留下来的 CHECK;RENAME COLUMN 不自动改写约束文本,引用旧名称的 CHECK 会使改名被拒绝。ALTER TYPE 依据新 schema 校验 CHECK、默认值和键能力,空表也不跳过定义校验;通过定义校验后仍检查已有行,再发布结构变更。
绑定查询保存依赖对象编号、schema 版本、列定义和直接及间接依赖的 DOMAIN。执行前复查,真实存储的每次读取还在相关库读锁内验证全部依赖;结构改变或对象替换明确报错。守卫不提供整条多请求查询的数据快照;旧目录的未知身份/版本保留兼容校验,旧存储未确认守卫则拒绝执行。
查询由 `Runtime.Query.bindQuery` 统一准备并绑定,输出按位置校验,保留重复列名。优化完成后将表达式、排序及分组键绑定到输入布局;物理列顺序不同会在存储边界整理,缺列或位置不符明确报错。
`ORDER BY` 输出序号保留到计划生成,选择对应投影表达式或 DISTINCT 输出槽位,不通过别名再次查找。非排序投影仍在 LIMIT 后求值。
### 聚合与排序的求值顺序
聚合计划合并投影、HAVING 和排序表达式中的当前层聚合,重复调用只计算一次。内部列使用 SQL 标识符无法声明的名称。
HAVING 强制形成分组,无分组键时将输入作为整体组。HAVING 过滤位于聚合算子 Aggregate 之后、去重与排序截断之前。
表达式排序先计算隐藏排序键并保留投影所需列,再排序、截断和投影。DISTINCT 排序映射到已有输出槽位。HAVING 和排序子查询同样参与结构依赖守卫与 SELECT 权限检查。
### 相关子查询的列查找
相关子查询会引用外层查询的列,因此列查找必须区分内外层作用域。
相关子查询按最近到最远的独立列作用域查找,最近层歧义不向外回退;二段限定引用只在最近声明该来源的层解析,裸列可继续向外查找。
`BoundSubquery` 保存父布局中的取值表达式,缓存按槽位和值区分;重绑定保留局部和外层身份,原位置失效时只允许唯一同名列重定位。
执行期外层行仍扁平化,尚无独立作用域或来源编号,结构改写须在绑定前完成。
### 写入求值与约束检查
写语句按目标 schema 绑定输入表达式和赋值目标,求值与校验遵循以下规则:
- INSERT 拒绝重复目标列,按输入顺序求值,再按 schema 顺序补默认值、NULL 和自增值。
- UPDATE 的重复赋值读取前一次修改结果。全部赋值后检查 CHECK,再检查最终行集的 NOT NULL、UNIQUE 和 PRIMARY KEY。
- 唯一性检查保留整数与整值浮点的一致语义,多个 NULL 不冲突。检查通过前不发布修改。
- CHECK、非空和唯一性按列槽位取值。INSERT 已有行及引擎 ALTER 输入行按原 schema 整理,缺列不跳过。
### 可复用的写计划
INSERT、UPDATE、DELETE 和已有表的结构变更由 `Engine.bindWrite` 生成不保存数据行的 `BoundWrite`,`executeWriteM` 在执行前复查结构,允许跨数据及统计变化复用。
DML 在绑定阶段固定 INSERT 输入槽位、UPDATE 目标槽位、DELETE 行布局和 CHECK / NOT NULL / UNIQUE / PRIMARY KEY 约束;复用时不重新解析或绑定这些内容,自增起点与行集约束仍取当前数据。
结构变更则在执行时对变更后的 schema 绑定并检查约束。
计划携带目标表、嵌套查询及传递 DOMAIN 依赖;ADD COLUMN / ALTER TYPE 还包含新声明类型的 DOMAIN。
建表、建删库、DOMAIN 管理和会话命令继续走各自入口,不属于已有表写计划。
### DOMAIN 约束的变更
DOMAIN 的 CHECK 在独立 VALUE 槽位绑定,继承默认值和非空限制按当前领域目录合成;表列保留原始声明。创建依赖 DOMAIN 的表或领域也通过结构守卫复查定义。
ALTER DOMAIN 的校验与提交按以下顺序执行:
1. 在存储库读锁内取得完整快照,校验直接及间接依赖和已有行。
2. 以库身份和 WAL 检查点作为令牌,在写锁内再次比较。
3. 令牌一致时,通过 `ReplaceSchema` 提交;期间任何普通提交都使令牌失效,并返回 `serialization_failure`。
约束变更复用现有目录版本、WAL 和恢复路径,不引入通用结构事务。
### 存储操作的结构守卫
写入前先发送只读的 `validate_write` 请求,确认存储支持写守卫并校验依赖;未确认时不发送实际修改请求。
实际读取在相关库共享门内复查,实际写入在目标库独占门及其他依赖库共享门内复查,跨库按库名排序取门。
对象替换或结构版本变化使旧绑定失效,包括结构改变后恢复原定义。
守卫保证结构校验与单次存储操作之间不发生 DDL,不提供整条写语句的数据快照或数据冲突检测。
### 修改表结构时的约束校验
CHECK 定义和执行共用类型、函数及列槽位绑定入口,拒绝聚合、子查询和非布尔条件。修改表结构时还需遵守以下规则:
- DROP COLUMN 校验保留下来的 CHECK。
- RENAME COLUMN 不自动改写约束文本,引用旧名称的 CHECK 会使改名被拒绝。
- ALTER TYPE 依据新 schema 校验 CHECK、默认值和键能力,空表也不跳过定义校验。通过定义校验后仍检查已有行,再发布结构变更。
### 查询优化的能力与边界
查询优化支持以下能力,统计信息只影响计划选择,优化不能改变查询结果语义:
| 能力 | 实现与边界 |
| --- | --- |
@@ -81,43 +195,87 @@ CHECK 定义和执行共用类型、函数及列槽位绑定入口,拒绝聚
| 成本估算 | 使用行数、不同值数和数值列的 16 桶等宽直方图 |
| 索引选择 | 优化器判断适用条件与成本,存储层管理实际索引 |
派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。执行器用查询内状态保存首次使用时成功求出的子查询行;键包含已解析查询定义与具名外层绑定值,跨不同父布局复用,原始名称调用保守包含整个外层行。派生表按执行子树和外层行缓存,应用来源别名前复用,优化后子树不同则分别执行。标量、IN、EXISTS 和 ANY / ALL 各自处理共享行集,保留基数错误和三值逻辑。缓存在每次执行时清空,不跨会话或数据修改保留;既有表达式分组求值顺序保持,JOIN 空行对仍不触发子查询。尚无磁盘溢出或物化内存预算,也不提供整条查询的数据快照。统计信息只影响计划选择,优化不能改变查询结果语义。
#### 连接重排与投影下推
`SemiJoin` 分别绑定两侧键,只暴露左侧布局;执行左侧为空时不求右侧,其他情况完整求右侧并构建键集合,按原左行顺序过滤,右侧重复键不会放大结果。独立 IN 保存原子查询身份,继续共享其他消费者的物化结果;NOT IN 跟踪右侧 NULL,NOT EXISTS 的空值键则视作不匹配。大整数与浮点混合比较超出安全哈希范围时退回既有逐值比较。重写只覆盖单独过滤谓词:IN 左键限普通列或简单字面量;相关 EXISTS 限单表、唯一外层等值引用和普通列/简单字面量投影。组合谓词、复杂相关条件、聚合、排序截断及可能报错的相关投影继续走原路径,避免改变错误时序和词法作用域。
派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。
完整 SQL 写法及限制见 [syntax.md](syntax.md)。
#### 子查询结果的复用
## 快照事务
执行器用查询内状态保存首次使用时成功求出的子查询行;键包含已解析查询定义与具名外层绑定值,跨不同父布局复用,原始名称调用保守包含整个外层行。
派生表按执行子树和外层行缓存,应用来源别名前复用,优化后子树不同则分别执行。标量、IN、EXISTS 和 ANY / ALL 各自处理共享行集,保留基数错误和三值逻辑。
缓存在每次执行时清空,不跨会话或数据修改保留;既有表达式分组求值顺序保持,JOIN 空行对仍不触发子查询。
尚无磁盘溢出或物化内存预算,也不提供整条查询的数据快照。
#### 用存在性匹配加速子查询
半连接与反连接根据右侧是否存在匹配行来筛选左行,只输出左侧列。`SemiJoin` 分别绑定两侧键,只暴露左侧布局;执行左侧为空时不求右侧,其他情况完整求右侧并构建键集合,按原左行顺序过滤,右侧重复键不会放大结果。
独立 IN 保存原子查询身份,继续共享其他消费者的物化结果;NOT IN 跟踪右侧 NULL,NOT EXISTS 的空值键则视作不匹配。
大整数与浮点混合比较超出安全哈希范围时退回既有逐值比较。
重写只覆盖单独过滤谓词:IN 左键限普通列或简单字面量;相关 EXISTS 限单表、唯一外层等值引用和普通列/简单字面量投影。
组合谓词、复杂相关条件、聚合、排序截断及可能报错的相关投影继续走原路径,避免改变错误时序和词法作用域。
完整 SQL 写法及限制见 [SQL 语法](syntax.md)。
## 快照事务与行锁
MVCC 是多版本并发控制:每条逻辑行有不复用的内部编号和版本链,事务读取开始时可见的版本及自身修改。库内提交序号使用 WAL 提交 LSN;`mvcc.json` 保存版本目录,在堆与索引同步后原子发布,再推进库检查点。旧目录首次写入或开始事务时导入版本,原页格式保留。
| 阶段 | 行为 |
| --- | --- |
| BEGIN | 将当前数据库读为会话内存快照 |
| 事务内执行 | 读取快照与自身修改,看不到其他连接后续提交 |
| SAVEPOINT | 保存当前快照;回滚可退回指定保存点 |
| COMMIT | 比对基线与现值,确认无写写冲突后原子应用差分 |
| ROLLBACK | 丢弃会话快照与保存点 |
| BEGIN | 注册存储事务和快照引用,向引擎返回一致的结构与行集 |
| 事务内执行 | 基于开始快照执行,修改行和唯一键在语句成功前取得锁 |
| SAVEPOINT | 保存工作行集和读依赖;回滚同步存储计划,已有锁保留到事务结束 |
| COMMIT | 在短提交门内合并最新版本、校验权限、对象、读写依赖及最终约束,再经 WAL 发布 |
| ROLLBACK / 断线 | 清除计划、快照引用、锁和等待边;保存点不能跨连接恢复 |
有整数 id 的表按行比对并提交差分;其他表按整表替换提交。发生冲突时返回 serialization_failure 并保留事务,由调用方回滚或重试。事务内不支持 DDL 或切换数据库。
行锁覆盖内部行编号及旧、新唯一值,NULL 不争用唯一值锁。同一库中不同行可以同时准备修改,等待行锁不占库提交门。等待图检测环并中止形成环的等待者;总等待上限为 5 秒,分别返回 `deadlock_detected` 或 `lock_timeout`。版本冲突返回 `serialization_failure`。调用方须 ROLLBACK,再从 BEGIN 重做,不能只重发 COMMIT。保存点回滚不释放此前取得的锁。
默认 BEGIN 提供快照隔离,复查写入行的内容版本。SERIALIZABLE 写事务另外按表复查所有读依赖,包括子查询及幻行,因此不同记录的变化也可能导致保守冲突。只读事务可完成原快照读取。事务内禁止 DDL 和切换数据库。
自动提交的查询与数据写语句也使用存储事务。权限和 DDL 使用全局独占门;数据准备使用共享门,同库提交校验和 WAL 发布仍按顺序完成,以保留连续的已应用提交前缀。原始管理员存储写入不取得 SQL 事务行锁,但会发布版本,旧事务随后按版本冲突规则拒绝提交。
SQL 工作行集仍在 Haskell 中物化;版本目录发布也会读取整库并写出版本文件。行版本支持隔离与回收,不表示已有按需取行、增量版本日志或吞吐量保证。结束事务释放内存中的旧版本,后续发布写回回收结果;旧版本及整库快照会增加内存、磁盘和 I/O 成本。
### 在线空间整理
显式 `compact` 将快照中的存活行复制到独立代次的影子堆与索引,复制期间继续接受 SQL 数据写入。切换前短暂独占库门,保留最新行集的有序前缀,删除影子中的过期行并按最新顺序追加其余行;并发变化较多时,切换窗口可能重写较长后缀。
影子文件同步完成后,以 WAL 的 Migrate 操作发布 catalog 中的 `file_generation`,逻辑对象编号和结构版本不变。旧快照结束后删除退役文件;重启先恢复切换日志,再清理未发布与退役代次。启动时隐藏列整理仍使用离线重写。备份须包含整个库目录及页侧文件,不能只复制 catalog 指向的新堆。
## WAL 与崩溃恢复
预写日志记录已提交的修改,检查点记录已完成刷盘的位置。写入和恢复遵循以下顺序:
1. 一组写操作与提交标记写入 WAL,每条记录带递增 LSN。
2. 数据和 catalog 刷盘后,检查点推进到已完成提交的 LSN。
3. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
4. 重启时只重放检查点之后的已提交组,丢弃未提交组,并进行幂等检查。
2. 数据页和索引页覆盖前,完整页镜像与校验和先同步到对应 `.page-wal` 文件。
3. 数据文件同步后,原子替换 `.pages` 中的页 LSN 和写序号,再清理镜像日志。
4. 数据、页版本和 catalog 刷盘后,同步并原子发布行版本目录,再将库检查点推进到已完成提交的 LSN。
5. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
6. 重启时先按有效全页镜像修复撕裂写,再重放库检查点之后的已提交组,丢弃未提交组,并进行幂等检查。
建表、删表、建索引、删索引和数据修改均进入 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。
页镜像有独立递增写序号,允许同一事务多次刷新同一页;不能仅凭相同页 LSN 跳过后一次写入。未完整写出的尾帧丢弃,完整帧校验失败则明确报错并保留材料。镜像写入或检查点失败后,当前页句柄拒绝继续刷新,析构也不能绕过恢复。
若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见 [bootstrap.md](bootstrap.md)。
已提交组恢复前丢弃受影响表的派生索引,再从堆数据重建,避免残缺索引根阻断逻辑重放。整表日志直接重写,不先依赖可能不完整的行集判断是否已应用。
建表、删表、建索引、删索引、账号和数据修改均进入带提交点的 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。
若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见[系统引导与恢复](bootstrap.md)。
## 页面与空间回收
- 删除行先将槽位标记为墓碑。
- 死空间达到页大小的四分之一时,随删除整理页内活行。
- 整理保留槽位编号,索引中的页号与槽号仍有效。
- compact 通常逐页整理;存在 DROP COLUMN 留下的隐藏列时整表重写。
- 显式 compact 在线复制存活行、补齐并发修改并经 WAL 切换文件代次,回收跨页空洞及隐藏列字节,同时重建索引位置。
不进行跨页填洞;没有 ORDER BY 时仍保留当前扫描顺序,但应用应使用显式排序获得稳定结果。
跨页整理的复制阶段允许并发数据写入,最终追平与切换仍独占库门。旧快照引用阻止退役文件回收。整理保留切换时的逻辑行序;应用应使用 ORDER BY 获得确定结果。
## 身份、授权与系统目录
@@ -132,13 +290,15 @@ CHECK 定义和执行共用类型、函数及列槽位绑定入口,拒绝聚
| 稳定对象 | catalog 分配数据库及表编号,授权与所有者绑定对象和身份编号,删除后不复用 |
| 所有者 | 建表者获得表所有权;启用角色可继承所有权,数据库所有权不隐含他人表的数据权限 |
对象权限目录 __system_object_acl 对普通语句和存储请求隐藏。权限快照按字符分块,在系统库写锁内比较版本并通过同一 WAL 组替换;服务启动恢复建表前持久化的创建者声明,旧名称授权只迁移一次到现存稳定对象。
对象权限目录 __system_object_acl 对普通语句和存储请求隐藏。
类型目录说明见 [types.md](types.md),身份字段与权限边界见 [users-and-roles.md](users-and-roles.md)。
权限快照按字符分块,在系统库写锁内比较版本并通过同一 WAL 组替换;服务启动恢复建表前持久化的创建者声明,旧名称授权只迁移一次到现存稳定对象。
类型目录说明见[类型与类型目录](types.md),身份字段与权限边界见[用户、角色与属性](users-and-roles.md)。
## 权限变更与会话失效
账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员及转授权来源关系,确定涉及的登录身份。共享读写门允许查询并行,并将授权及对象结构变更与查询排序;事务提交再次验证权限和稳定对象编号。
账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员及转授权来源关系,确定涉及的登录身份。共享读写门允许查询并行,并将数据写入、提交、授权及对象结构变更与查询排序;事务提交再次验证权限和稳定对象编号。
TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不被强行中断。权限逐条查询,避免继续使用旧授权。Web 会话还通过账号版本校验,口令或身份变更使旧凭据失效。
@@ -147,7 +307,7 @@ TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不
> [!NOTE]
> 以下是规划,不能作为当前 SQL、事务或恢复能力使用。进度与验收见 `.agents/context/progress.txt`。
### bound IR
### 查询内部表示的后续完善
已有列槽位、类型化函数与 `BoundQuery`;后续补齐多级词法作用域、来源/列身份,以及写语句与约束的统一绑定。迁移保留同名遮蔽、NULL / DOMAIN、输出布局和错误时序,结构改写应在绑定前完成或重新绑定。
@@ -160,10 +320,4 @@ TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不
### 存储扩展
| 方向 | 前置条件与边界 |
| --- | --- |
| 行版本 MVCC | 稳定且不复用的 `row_id`、版本链与快照序号;提交复查写冲突和唯一键,旧索引候选随活跃快照保留 |
| 跨页 compact | MVCC 与稳定定位映射,影子文件经 WAL 原子切换;保留旧读引用和逻辑扫描顺序 |
| 并行写 | 固定对象/行/唯一键锁序及取消机制,WAL/CSN 发布保持连续已应用前缀;DDL、权限和目录变更继续排序 |
新页格式、版本化 WAL、离线升级和恢复验证是前提。升级在停服副本上保留备份并校验后发布;旧二进制不得打开不支持的格式。任何持久化应用失败须保持 `recovery_required`,不将已提交 WAL 组当作失败撤销。性能收益不能代替语义、权限和崩溃恢复验证。
行版本、行锁、同库数据准备与在线整理的边界见上文。后续优化须根据测量减少整库物化、版本文件写放大和最终切换窗口;WAL 发布顺序、版本可见性与恢复约束保持一致。
+78
View File
@@ -0,0 +1,78 @@
# 博客业务数据库接入
本页帮助 ChuBlog 开发者接入业务数据,运行真实 Rust 驱动验收,并区分数据库已验证的能力与博客服务端需要实现的规则。正文和资源保存在 Git 文件中,数据库只保存业务状态和可重建索引。
## 数据模型
[验收表定义](../examples/blog/schema.sql) 在空库创建表,采用现有标量类型及 CHECK,不依赖 ENUM、外键或复合索引。它是供 ChuBlog 接入时采用的基线,不会自动替换 ChuBlog 当前文件账户实现,也不对已有库执行结构迁移。
PHC 是包含算法、版本、参数、盐和哈希的密码编码字符串。
| 数据 | 表 | 恢复要求 |
| --- | --- | --- |
| 用户、完整密码 PHC、角色、撤权版本 | blog_users | 必须备份 |
| 发布路径与 SSH 公钥 | blog_publish_grants、blog_ssh_keys | 必须备份 |
| 可见性、查阅口令 PHC、撤销版本、隐藏、删除、精华与计数 | blog_article_state | 必须备份 |
| 评论与匿名或登录身份关联 | blog_comments | 必须备份 |
| 点赞、收藏的活动状态与去重键 | blog_reactions | 必须备份 |
| 管理审计与迁移完成标记 | blog_audit、blog_migrations | 必须备份 |
| 文件路径、标题与 Git 提交索引 | blog_articles | 可从 Git 重建 |
索引重建只能修改派生表。站长隐藏或删除、授权版本和查阅口令不能从文章元数据推导或被作者再次推送覆盖。停机备份应包含整个存储数据目录;正文仓库仍按 ChuBlog 的备份流程保存。
## 事务与权限检查
需要先检查再写入的操作使用独立连接上的 `BEGIN ISOLATION LEVEL SERIALIZABLE`,包括注册、发评论、点赞或收藏、撤权及物理删除关联数据。读取身份、文章状态和关联记录,检查结果后再更新明细、计数和审计,最后 COMMIT。不要把多个请求的事务交给共享的驱动 Client 克隆;克隆共用一个会话。
串行化模式在写事务提交时按表复查读依赖,包括子查询和幻行。其他连接改变读过的表,即使改的是不同行,也可能返回 `serialization_failure`。默认 BEGIN 使用行版本快照和行级写冲突检查,两种模式都校验合并后的最终列约束;工作行集仍需整库物化。
冲突行或唯一键会等待持锁事务结束,等待不占库提交门。死锁和超过 5 秒的锁等待分别返回 `deadlock_detected`、`lock_timeout`,事务已中止。收到这两个错误或 `serialization_failure` 后先 ROLLBACK,再从 BEGIN 重做业务检查;不要只重发 COMMIT。SAVEPOINT 回滚恢复数据与读依赖,已取得的锁保留到整个事务结束。
断线或超时后的提交结果可能未知。先通过稳定操作键查询结果,不盲目重做计数更新。生产中的操作键应由服务端按操作身份生成,例如将规范化的文章、主体和互动类别编码后计算 SHA-256,保存完整 64 位十六进制字符串;验收使用可读的固定键。匿名身份关联或合并也须在同一事务处理,避免用客户端任意提交的用户名去重。
本模型没有数据库外键。新增关联记录的事务必须检查被引用对象;物理删除对象的事务必须检查及处理关联记录,双方均采用串行化模式。逻辑删除保留业务对象,查询时过滤删除与隐藏状态。博客服务端检查角色继承、作者所有权、撤权版本、SSH 发布和访问授权,业务用户不需要各建数据库身份。
受保护访问遇到数据库查询、身份或哈希校验错误时直接拒绝,不回退为公开内容,也不使用过期的授权结果。博客需将会话和已签发访问凭据绑定到对应版本;数据库只持久化版本和哈希,不执行 HTTP 或 SSH 授权。
## 旧文件账户迁移
[驱动验收程序](../examples/blog/acceptance.rs) 使用当前 accounts.rs 的凭据和授权字段构造迁移副本,原样保存完整 Argon2id PHC。PHC 包含算法、版本、参数、盐和哈希;不导入明文,不重新哈希旧凭据。文章查阅口令也保存独立加盐的完整 PHC。
| 旧授权 | 新角色 |
| --- | --- |
| enabled=false | BANNED |
| reader | MEMBER |
| publisher | AUTHOR |
| admin | MASTER |
迁移先校验两个文件的账号集合和字段,再在单次事务中写入账户、路径、公钥和完成标记。源文件保留;失败不留部分数据,同一来源重复执行由完成标记识别。缺少对应授权的凭据必须人工核对,不自动提升权限。
该程序是迁移验收样例,不读取生产文件。ChuBlog 需要配套实现停写、源文件权限校验、备份、生产文件读取、计数核对和切换流程。哈希计算、参数升级、密码限流和公钥格式校验也由博客完成。
## 评论、互动与容量
评论分页使用 `ORDER BY created_at, id`,下一页以最后一条的时间和 id 为游标,避免同一时间记录遗漏。样例建立文章和时间索引;热度可按活动互动与可见评论聚合、稳定排序,并在串行化事务内重算计数。权重、时间衰减和防刷规则由博客定义。
点赞或收藏将主体、文章及类别作为稳定去重键,只在活动状态改变时更新计数。重复取消和删除不得重复扣减;计数字段的 CHECK 拒绝负数。删除评论和互动应和相应计数、审计一起提交。
接入基线建议限制评论为 256 个 Unicode 字符且 UTF-8 不超过 768 字节,用户名 3 至 32 个 ASCII 字符,单条发布路径 256 字节,SSH 公钥 1024 字节。博客还应限制每用户路径数量、请求体和累计配额,逐字段限制不能替代整行容量校验。数据库默认 4096 字节页,整行超限会在 WAL 前拒绝;无长行或溢出存储。
标量索引键按 UTF-8 字节计算,默认配置最多 128 字节,覆盖 32 字节作者名、一个分隔符与 80 字节文章标识。有效上限还取决于页大小和 B+ 树分支数,密集旧配置可能仍只容纳 64 字节;不截断键。原索引无需重建即可打开,首次写长键会更新文件头的键长能力;写入长键后不得降级到仅支持 64 字节的旧二进制。
## 运行验收
前提是已按[门禁说明](../gate-check/README.md)重建 release 动态库、服务端和引导程序,且本地有 ChuBlog 的实际 Rust 驱动及 Cargo 依赖缓存。验收不使用 Docker,也不验证 Dockerfile 的镜像源。
1. 准备当前构建的 `chusql-server` 与 `csql-bootstrap` 的绝对路径。
2. 从 ChuSQL 根目录运行以下命令,替换两个二进制路径。驱动默认读取相邻 ChuBlog 仓库,可通过 `--driver` 指定其他路径。
```sh
python3 examples/blog/test-business.py \
--server /path/to/chusql-server \
--bootstrap /path/to/csql-bootstrap \
--driver ../ChuBlog/external/ChuSQL-RustDriver
```
3. 确认输出 `blog business: real driver, restart and cold backup restore passed`。日志和测试 Cargo 清单位于 `logs/blog-business/`;临时数据库与备份副本自动删除。
程序只使用临时目录和本机随机端口,单任务编译 Rust 验收程序。业务验收后启动连续关联写入,收到至少十次提交回执后强杀测试服务,再在重启和备份副本中核对全部回执及明细 / 计数一致性。它验证迁移、完整 PHC 校验、并发注册、权限变更、文章管理状态、索引重建、稳定评论分页、重复操作、计数与容量拒绝,并在服务重启和停机备份副本中重新核对业务数据。不据此宣称博客 HTTP、SSH、文件访问控制、匿名身份合并或生产迁移已实现。
+30 -8
View File
@@ -2,23 +2,35 @@
[← 返回 README](../README.md)
**本页导航:** [安装引导](#安装引导) · [三种维护策略](#三种维护策略) · [副本、备份与恢复边界](#副本备份与恢复边界)
本页内容:
`csql-bootstrap` 初始化系统目录,也能在服务无法启动时离线修复。
- [安装引导](#安装引导)
- [三种维护策略](#三种维护策略)
- [副本、备份与恢复边界](#副本备份与恢复边界)
本页面向安装和维护数据库的人员,帮助你选择初始化或离线修复方式。`csql-bootstrap` 初始化系统目录,也能在服务无法启动时离线修复。
系统目录保存账号、授权和类型等元数据。预写日志(WAL)保存可供恢复的写入记录。
> [!WARNING]
> 维护前必须停止使用同一数据目录的所有进程。数据目录锁拒绝并发维护,但不能依赖旧版进程具备该锁。
## 安装引导
```text
使用以下命令初始化系统目录,`settings.toml` 替换为实际配置文件路径:
```sh
csql-bootstrap --config settings.toml --user root --password-stdin
```
口令从标准输入的一行读取,哈希后保存。程序创建系统目录、启用且可登录的 SUPERUSER,以及现有内置类型登记项。重复引导保留已有口令和身份属性,拒绝冲突的类型定义及已降权的引导身份。安装脚本调用此入口。只有明确指定 `--passwordless` 才选择免密初始化。
口令从标准输入的一行读取,哈希后保存。程序创建系统目录、启用且可登录的最高权限身份(SUPERUSER),以及现有内置类型登记项。
安装脚本调用此入口。重复引导保留已有口令和身份属性,拒绝冲突的类型定义及已降权的引导身份。只有明确指定 `--passwordless` 才选择免密初始化。
## 三种维护策略
根据需要保留的数据和恢复范围选择命令:
| 命令 | 行为 | 数据影响 |
| --- | --- | --- |
| `repair` | 修复已知系统表的目录结构、列定义及结构元数据 | 不重放 WAL,不补初始化数据;原有 `.db` 文件必须保持字节一致 |
@@ -27,22 +39,32 @@ csql-bootstrap --config settings.toml --user root --password-stdin
`recovery` 是 `recover` 的兼容别名。三个命令均保留业务数据库目录;`reset` 后业务数据仍在,但原访问授权已清除,需要重新创建身份并授权。
```text
维护命令示例,配置路径需按实际位置替换:
```sh
csql-bootstrap repair --config settings.toml
csql-bootstrap recover --config settings.toml
csql-bootstrap reset --config settings.toml --user root --password-stdin
```
优先使用 `repair`。若必须恢复数据、补内置类型或初始化管理员,它会中止,保持原系统目录不变,并用英文提示使用 `recover` 或 `reset`。需要初始化缺失管理员时,`recover` 要求 `--password-stdin`。已有管理员的口令不会因提供该参数而重设。`reset` 要求提供口令输入选项或显式选择 `--passwordless`。
优先使用 `repair`。若必须恢复数据、补内置类型或初始化管理员,它会中止,保持原系统目录不变,并用英文提示使用 `recover` 或 `reset`。
需要初始化缺失管理员时,`recover` 要求 `--password-stdin`。已有管理员的口令不会因提供该参数而重设。`reset` 要求提供口令输入选项或显式选择 `--passwordless`。
维护入口直接使用存储库,不依赖数据库服务能够启动,不提供 SQL 或 TCP 远程维护接口。运行时输出、错误和服务启动修复提示均使用英文。
## 副本、备份与恢复边界
程序在数据目录下的 `bootstrap-work-<时间戳>/system` 副本中执行并验证。验证失败时不替换原目录;候选副本保留供检查。成功时将原系统目录移为 `system-backup-<时间戳>`,再发布新目录。输出给出备份与候选路径。备份包含口令哈希和授权,应按数据库数据同等保护。
程序在数据目录下的 `bootstrap-work-<时间戳>/system` 副本中执行并验证。验证失败时不替换原目录;候选副本保留供检查。
成功时将原系统目录移为 `system-backup-<时间戳>`,再发布新目录。输出给出备份与候选路径。备份包含口令哈希和授权,应按数据库数据同等保护。
发布第二次目录移动失败时会尝试回滚。若发布期间进程或机器崩溃,先停止所有进程,检查输出及上述目录;保存候选与当前目录后,可将完整的备份恢复为数据目录下的 `system`。不要把不同版本或不同时间点的零散文件混合覆盖。
WAL 只能恢复尚且可用的已提交记录,不能保证恢复已截断、丢失或损坏的历史。已知缺失表的记录数无法恢复完整时会拒绝发布。损坏的数据页也可能使 `recover` 失败,此时需要完整备份或选择清除系统内容的 `reset`。
目录元数据严重损坏时,只能依据程序内置定义重建已知系统表。存在无法识别结构的系统表文件时拒绝重建,应从备份恢复或使用 `reset`。身份目录计数器损坏后只能依据现存身份编号重建,无法还原已删除身份的历史编号上限。业务数据库自身的结构、数据页和 WAL 不由这些命令修复。
### 无法恢复的情况
目录元数据严重损坏时,只能依据程序内置定义重建已知系统表。存在无法识别结构的系统表文件时拒绝重建,应从备份恢复或使用 `reset`。
身份目录计数器损坏后只能依据现存身份编号重建,无法还原已删除身份的历史编号上限。业务数据库自身的结构、数据页和 WAL 不由这些命令修复。
+51 -10
View File
@@ -2,13 +2,19 @@
[← 返回 README](../README.md)
**本页导航:** [组件命令](#组件命令) · [csql](#csql) · [chusql-web](#chusql-web) · [chusql-server](#chusql-server) · [范式分析](#范式分析)
本页内容:
ChuSQL 的主要用户命令如下:
- [组件命令](#组件命令)
- [csql](#csql)
- [chusql-web](#chusql-web)
- [chusql-server](#chusql-server)
- [范式分析](#范式分析)
本页面向命令行和 Web 使用者,说明启动参数、交互命令及编辑事务的行为。CLI 指命令行客户端。
类型见 [类型与函数](types.md),身份与授权见 [用户与角色](users-and-roles.md)。
SQL 的查询、事务、类型、用户与授权语法见 [syntax.md](syntax.md)。
查询、事务、类型、用户与授权语法见 [SQL 语法](syntax.md)。
## 组件命令
@@ -47,13 +53,17 @@ csql [options]
| `--config FILE` | 指定配置文件 |
| `-h, --help` | 显示帮助 |
CLI 历史只记录完整 SQL 和元命令。包含 `IDENTIFIED` 或 `PASSWORD`(忽略大小写)的输入不记录,多行口令输入和未完成语句也不会逐行写入历史;包含这些文本的普通字符串或标识符同样会被排除。已有历史文件不会自动清理。含这些文本的 SQL 解析错误会隐藏输入原文。
### 历史记录与口令输入
示例:
CLI 历史只记录完整 SQL 和元命令。包含 `IDENTIFIED` 或 `PASSWORD`(忽略大小写)的输入不记录,多行口令输入和未完成语句也不会逐行写入历史;包含这些文本的普通字符串或标识符同样会被排除。
已有历史文件不会自动清理。含这些文本的 SQL 解析错误会隐藏输入原文。
示例假定 `sales.orders` 已存在:
```sh
csql -d sales -e "SELECT * FROM orders LIMIT 5"
csql -f json -e "SELECT count(*) FROM orders"
csql -d sales -f json -e "SELECT count(*) FROM orders"
```
在脚本里非交互使用(配合 `-e`):
@@ -101,11 +111,27 @@ chusql-web [options]
### 表格编辑与事务
首次打开业务数据表时建立编辑快照。同一页面内、同一数据库的跨表修改统一暂存;“提交全部”在服务端事务中执行,全部成功才持久化。逐项撤销只移除本地编辑项,下次提交从原快照重放剩余编辑;“撤销全部”执行 ROLLBACK 并重新加载表和统计。
首次打开业务数据表时建立编辑快照。同一页面内、同一数据库的跨表修改统一暂存。
编辑操作的效果如下:
| 操作 | 效果 |
| --- | --- |
| 提交全部 | 在服务端事务中执行,全部成功才持久化 |
| 逐项撤销 | 只移除本地编辑项,下次提交从原快照重放剩余编辑 |
| 撤销全部 | 执行 ROLLBACK,重新加载表和统计 |
### 编辑失败与提交重试
途中发生约束错误时,整批语句退回编辑保存点,完整编辑内容仍可修正。写写冲突不会覆盖其他会话,必须撤销并重新加载后重新编辑。提交响应断线时冻结编辑集;再次点击提交使用同一标识确认或重试同一批修改,已成功的提交不会重复执行。
编辑事务绑定登录会话、页面和数据库;待提交修改存在时,先提交或撤销才能换库或运行 SQL。关闭最后一个表标签且没有待提交修改时释放快照。关闭页面会请求回滚,退出登录会关闭连接并丢弃快照;若页面断线、回滚请求未送达,后台每分钟按配置的会话空闲与最长寿命清理连接。重新登录不能继续旧事务。
### 页面关闭与快照释放
编辑事务绑定登录会话、页面和数据库;待提交修改存在时,先提交或撤销才能换库或运行 SQL。
关闭最后一个表标签且没有待提交修改时释放快照。关闭页面会请求回滚,退出登录会关闭连接并丢弃快照;若页面断线、回滚请求未送达,后台每分钟按配置的会话空闲与最长寿命清理连接。重新登录不能继续旧事务。
### SQL 控制台事务
SQL 控制台使用另一条服务端连接;SQL 的 BEGIN / COMMIT / ROLLBACK 绑定发起它的页面与查询标签。事务尚未结束时,其他控制台和普通 REST 写操作不能占用该连接;关闭所属查询标签或页面时请求回滚。系统身份修改不支持数据事务,账号表只读,使用 SQL 用户与角色命令管理。
@@ -133,9 +159,13 @@ chusql-server [options]
## 范式分析
范式分析检查表结构是否符合声明的业务依赖,用于发现结构上的冗余。schema 指列名、类型和约束等表结构信息。
分析只读取 schema 和业务依赖声明,要求当前数据库的 `CONNECT` 与目标表的 `SELECT` 权限;系统对象拒绝分析。它不检查样本数据,也不执行数据迁移。
声明文件使用 UTF-8 JSON,字段名须与表结构一致:
声明文件使用 UTF-8 JSON,字段名须与表结构一致。`declared_keys` 声明键列,`dependencies` 声明函数依赖,即哪些列能唯一决定其他列;`lhs` 是决定列,`rhs` 是被决定列。
`dependencies_complete` 表示声明是否覆盖全部业务依赖,`atomic_attributes` 表示字段是否具有业务上的原子性。示例如下:
```json
{
@@ -146,6 +176,8 @@ chusql-server [options]
}
```
将声明保存为 `dependencies.json` 后,执行以下命令。示例假定 `school` 数据库及 `enrollment` 表已存在:
```sh
csql -d school --normalize enrollment --declaration dependencies.json --format json
```
@@ -161,4 +193,13 @@ csql -d school --normalize enrollment --declaration dependencies.json --format j
报告包含候选键、1NF / 2NF / 3NF / BCNF 结论、违反证据及一次 BCNF 分解建议。子关系可能仍需分析,分解不保证依赖保持,也不证明现有 SQL 数据满足声明。Web 使用发起页面或控制台的事务视图;对象或 schema 变化须重新分析。
最多分析 12 列,依赖和键声明各最多 256 项,运行预算 5 秒;超限、权限或输入错误明确返回失败,不截断为完整报告。CLI 成功获得报告时退出码为 `0`,即使结论违反或信息不足;请求失败时退出非零。
分析的资源限制和退出码如下:
| 项目 | 限制或行为 |
| --- | --- |
| 表列数 | 最多 12 列 |
| 依赖声明、键声明 | 各最多 256 项 |
| 运行时间 | 预算 5 秒 |
| 超限、权限或输入错误 | 明确返回失败,不把截断结果当成完整报告 |
| CLI 成功获得报告 | 退出码为 `0`,包括违反范式或信息不足的结论 |
| CLI 请求失败 | 退出码非零 |
+22 -7
View File
@@ -2,9 +2,15 @@
[← 返回 README](../README.md)
**本页导航:** [存储配置](#存储配置) · [Web 配置](#web-配置) · [Server 配置](#server-配置) · [账号与安全](#账号与安全) · [修改配置](#修改配置)
本页内容:
ChuSQL 使用统一配置文件 `settings.toml`。默认位置:
- [存储配置](#存储配置)
- [Web 配置](#web-配置)
- [Server 配置](#server-配置)
- [账号与安全](#账号与安全)
- [修改配置](#修改配置)
本页帮助你查找配置项、修改监听端口和数据路径。ChuSQL 使用统一配置文件 `settings.toml`,默认位置如下:
| 平台 | 路径 |
| --- | --- |
@@ -45,7 +51,7 @@ ChuSQL 使用统一配置文件 `settings.toml`。默认位置:
| `password_min_length` | `12` | 普通账号最短口令长度 |
| `password_classes` | `2` | 口令至少包含的字符类别数 |
监听地址见上表,静态资源目录固定为 `static`(相对安装目录),管理员账号名不在配置里、由启动参数 `--user` 指定(默认 `root`);后两项不能用配置项改写。
静态资源目录固定为安装目录下的 `static`。管理员账号名由启动参数 `--user` 指定,默认为 `root`;这两项不能通过配置文件修改。
## Server 配置
@@ -56,7 +62,9 @@ ChuSQL 使用统一配置文件 `settings.toml`。默认位置:
| `max_message` | `1048576` | 单条请求最大大小(字节) |
| `max_rows` | `1000` | 单次查询最大返回行数 |
本机 sudo 认证额外使用 `[server] sudo_auth_user`,默认空字符串,表示关闭。设置为一个已启用 `ALLOW_SUDO_AUTH` 的数据库身份后,必须以 Unix root 或 Windows 提升后的管理员运行 `chusql-server`。这是明确的本机身份映射,与启动参数 `--user` 和最高权限属性分别设置。配置变更后重启服务生效,Web 设置页面不提供此项。操作步骤见 [用户与角色](users-and-roles.md#本机-sudo-认证)。
本机 sudo 认证额外使用 `[server] sudo_auth_user`,默认空字符串,表示关闭。设置为一个已启用 `ALLOW_SUDO_AUTH` 的数据库身份后,必须以 Unix root 或 Windows 提升后的管理员运行 `chusql-server`。
这是明确的本机身份映射,与启动参数 `--user` 和最高权限属性分别设置。配置变更后重启服务生效,Web 设置页面不提供此项。操作步骤见 [用户与角色](users-and-roles.md#本机-sudo-认证)。
> [!NOTE]
> 修改 Server 配置后须重启 `chusql-server`。sudo 映射默认关闭,启用时还须满足身份属性和操作系统提升要求。
@@ -77,11 +85,18 @@ ChuSQL 使用统一配置文件 `settings.toml`。默认位置:
配置可通过以下方式修改:
1. 直接编辑 `settings.toml`;
2. 通过 Web 设置页面修改。
- 直接编辑 `settings.toml`。
- 通过 Web 设置页面修改。
大多数配置在相关服务下次启动时生效。
安装器选择 `resources/settings.toml.linux` 或 `resources/settings.toml.windows`,填入当前用户的绝对路径后释放为 `settings.toml`。两份模板逐项提供英文注释。
Linux 数据目录为 `${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data`,日志目录为 `${XDG_STATE_HOME:-$HOME/.local/state}/chusql/logs`。Windows 数据和日志分别为 `%LOCALAPPDATA%\ChuSQL\data` 与 `%LOCALAPPDATA%\ChuSQL\logs`。安装参数 `--data-dir` / `-DataDir` 可覆盖数据目录,指定安装目录不会改变默认数据位置。
默认数据和日志路径如下:
| 平台 | 数据目录 | 日志目录 |
| --- | --- | --- |
| Linux | `${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data` | `${XDG_STATE_HOME:-$HOME/.local/state}/chusql/logs` |
| Windows | `%LOCALAPPDATA%\ChuSQL\data` | `%LOCALAPPDATA%\ChuSQL\logs` |
安装参数 `--data-dir` / `-DataDir` 可覆盖数据目录。指定安装目录不会改变默认数据位置。
+204
View File
@@ -0,0 +1,204 @@
# 用 Docker 部署 ChuSQL
本页帮助你在 Linux 容器中启动数据库和可选的 Web 管理端,并保留数据、备份及升级。需要 Docker Engine 或启用 Linux 容器的 Docker Desktop,以及 Docker Compose v2 或更新版本。
以下命令在 Linux 或 WSL 的 Bash 中执行。WSL 或小内存机器应先按[受限构建说明](#在-wsl-或内存受限的机器上构建)生成镜像,再启动服务。
默认服务只向宿主机的 127.0.0.1 发布端口。镜像包含数据库、引导程序、CLI 和 Web,服务以 UID/GID 10001 运行。Web 通过 TCP 连接数据库,不直接打开数据库目录。
## 启动数据库
在仓库根目录执行以下步骤。首次构建需要联网下载 Rust、GHC、Stack 和依赖,耗时取决于网络与构建缓存。
1. 创建本机口令文件。目录仅允许当前用户访问;文件需要让容器中的非 root 用户可读。以下交互不将口令写入 shell 历史:
```sh
install -d -m 700 secrets
bash -c 'read -r -s -p "管理员口令: " password; printf "\n"; printf "%s\n" "$password" > secrets/admin_password'
chmod 644 secrets/admin_password
```
文件首行必须至少含一个非空白字符,建议使用强口令。不要将口令文件加入版本控制或镜像;仓库的 Git 与 Docker 忽略规则已排除 `secrets/`。
2. 检查配置,然后构建并启动:
```sh
docker compose config --quiet
docker compose build server
docker compose up -d --wait server
```
Compose 将文件作为 [Docker secret](https://docs.docker.com/compose/how-tos/use-secrets/) 挂载到数据库容器。启动脚本先执行现有引导程序,再启动数据库;引导失败时不启动服务。已有口令、业务数据和类型目录不会被重复初始化覆盖。
3. 检查状态并打开 CLI:
```sh
docker compose ps
docker compose logs --tail=100 server
docker compose exec server csql --config /etc/chusql/web.toml --user root
```
CLI 使用同一容器网络中的 `server:7777`。输入口令后执行 `SHOW DATABASES;`,应看到系统数据库。健康检查通过实际 TCP `ping` 响应判断服务是否可用,不需要管理员口令。
首次初始化的管理员默认为 `root`。修改口令文件不会重设已有账号口令;修改方法见[账号与权限命令](users-and-roles.md)。重启时仍需保留可读的口令文件。
Dockerfile 默认从腾讯云内网 Debian 镜像源 `http://mirrors.tencentyun.com` 安装系统依赖,继续使用 Debian 的软件包签名校验。其他网络环境可用构建参数覆盖,例如 `docker compose build --build-arg DEBIAN_MIRROR=http://deb.debian.org server`;切换该参数不改变运行配置。
Rust 依赖默认通过 rsproxy 下载,保留 `Cargo.lock` 的版本与校验和检查。其他镜像可通过 `--build-arg CARGO_REGISTRY=sparse+https://镜像地址/index/` 指定;该地址须提供 Cargo sparse 索引及其下载配置。
## 启用 Web 管理端
镜像构建完成后,启用 `web` profile:
```sh
docker compose --profile web up -d --wait
```
在浏览器打开 <http://127.0.0.1:7778>,使用数据库账号登录。Web 仅在数据库健康后启动,使用 HTTP 健康接口检查自身状态。Web 容器不挂载管理员口令文件。
停止全部服务但保留数据:
```sh
docker compose --profile web down
```
`down -v` 会删除本部署的命名卷,包括数据库。日常停止和更新不要添加 `-v`。
## 配置端口、目录与资源
以下变量可在执行命令前导出,或放入本机 `.env`。它们不是管理员口令:
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `CHUSQL_IMAGE` | `chusql:local` | 运行镜像,可指定验收过的镜像 ID 或仓库 digest |
| `CHUSQL_ADMIN_USER` | `root` | 初始化及启动使用的管理员名 |
| `CHUSQL_ADMIN_PASSWORD_PATH` | `./secrets/admin_password` | 宿主机口令文件路径 |
| `CHUSQL_BIND_ADDRESS` | `127.0.0.1` | 宿主机发布地址 |
| `CHUSQL_TCP_PORT` | `7777` | 宿主机 TCP 端口 |
| `CHUSQL_WEB_PORT` | `7778` | 宿主机 HTTP 端口 |
例如,将 HTTP 端口改为 8080:
```sh
CHUSQL_WEB_PORT=8080 docker compose --profile web up -d --wait
```
容器内端口保持 7777 和 7778。更改宿主机发布地址可允许远程访问;TCP 和默认 HTTP 都不提供 TLS,应在可信网络使用,或通过 SSH 隧道、VPN、HTTPS 反向代理接入。使用 HTTPS 时在 `docker/web.toml` 中将 `cookie_secure` 设为 `true`。
启动配置分别在 `docker/server.toml` 和 `docker/web.toml`,由 Compose 只读挂载。数据库配置中的 `0.0.0.0` 是监听地址,Web 配置中的 `server` 是数据库连接地址。修改宿主文件后重新创建对应服务;Web 页面不能保存只读的启动配置。其余参数见[配置选项](config.md),已有数据的页大小和 B+Tree 阶数不能随意更改。
Compose 创建以下命名卷,实际名称带项目名前缀,默认是 `chusql_`:
| 卷 | 容器路径 | 内容 |
| --- | --- | --- |
| `data` | `/var/lib/chusql` | 系统目录、业务库、索引、WAL 与检查点 |
| `logs` | `/var/log/chusql` | 存储诊断日志 |
| `web_settings` | `/opt/chusql/resources` | Web 编辑器偏好 |
数据库目录只允许一个 server 使用,不要扩容 server 副本或让另一进程同时打开此卷。若改为宿主目录绑定,先为 UID/GID 10001 设置写权限;命名卷会使用镜像目录的所有者。
从运行 UID 不同的旧镜像迁移时,已有卷的所有者不会自动变化。例如旧 ChuBlog 数据库镜像使用 UID/GID 10003。先停止所有访问该目录的服务并完成备份,确认 Compose 挂载的是要迁移的卷,再用新镜像调整所有者:
```sh
docker compose run --rm -T --no-deps --user 0:0 --entrypoint chown server \
-R 10001:10001 /var/lib/chusql /var/log/chusql
```
随后启动服务,使用原口令检查原数据及新增写入。只在迁移所有者时使用 root,正常服务继续以 UID/GID 10001 运行。
每个运行中的服务默认限制为 1 核、512 MiB、128 个进程或线程,不使用额外交换空间。大数据量、整库快照事务或大型查询可能需要在 `compose.yaml` 中提高 `mem_limit` 和 `memswap_limit`;达到内存限制可能使容器被终止,重启后由 WAL 恢复。
### 在 WSL 或内存受限的机器上构建
运行容器的限制不限制镜像构建。Dockerfile 已将 Cargo 和 Stack 构建并发固定为 1,GHC 使用单个编译任务,并将 Haskell 构建进程的堆上限设为 1536 MiB。链接器、Rust 编译器、Stack 和 Docker 仍有额外内存开销;应为构建环境至少预留 4 GiB 可用内存并关闭其他大型构建。
需要在本机构建时,可使用 Docker Buildx 的[受限容器构建器](https://docs.docker.com/build/builders/drivers/docker-container/)。以下创建命令只需执行一次,后续复用同名构建器:
```sh
docker buildx create --name chusql-limited --driver docker-container \
--driver-opt memory=3g,memory-swap=3g,cpu-period=100000,cpu-quota=100000
docker buildx build --builder chusql-limited --load -t chusql:local .
docker buildx stop chusql-limited
```
构建器限制为 1 核、3 GiB 内存且不使用额外交换空间。达到限制会导致构建失败;不要为完成构建而在内存紧张的 WSL 中取消限制。这里生成的标签与 Compose 一致,可直接执行 `docker compose up -d --wait server`。
若 WSL 曾因资源不足退出,优先在构建服务器上生成镜像,再导入部署机器。不要同时构建多个镜像或运行全量测试。构建机使用:
```sh
docker build -t chusql:local .
docker save chusql:local -o chusql-image.tar
```
将 `chusql-image.tar` 复制到部署机器后执行:
```sh
docker load -i chusql-image.tar
docker compose --profile web up -d --wait
```
镜像没有预先发布到公共仓库,`chusql:local` 是本地构建的标签。首次构建若下载失败,应检查 Docker 镜像源和依赖站点访问;已有宿主 Stack/Cargo 目录不会被发送到构建上下文。
容器构建器不自动继承 Docker Engine 的镜像源。若官方仓库不可达,按本机网络配置创建 BuildKit TOML 文件,并在 `docker buildx create` 时通过 `--buildkitd-config <文件路径>` 指定;在 `[registry."docker.io"]` 中设置可信 `mirrors`,在 `[worker.oci]` 中设置 `max-parallelism = 1`。切换镜像源时保留上述 CPU 与内存限制。
若代理在镜像源模式下返回错误,但直接访问正常,可添加 `--build-arg BASE_REGISTRY=docker.m.daocloud.io`,显式选择提供 `library/` 路径的可信 Docker Hub 代理。三个基础镜像均固定 digest,替换地址不会接受内容不同的镜像;更新基础镜像时须在 Dockerfile 中更新 digest 并重新验收。
## 验证部署镜像
构建完成后,在仓库根目录运行以下命令,需要 Python 3。验收使用随机命名的独立容器和数据卷,不连接已有部署;每个数据库容器限制为 1 核、512 MiB,结束后删除本次创建的容器和卷。
```sh
sh docker/test-entrypoint.sh
python3 docker/test-runtime.py --image chusql:local --seconds 60
```
脚本验证协议登录、账号与授权、事务冲突和回滚、持续关联写入、SIGKILL 后恢复,以及停机备份向空卷恢复。磁盘满场景只填满容器内 16 MiB 的临时文件系统,将测试库的检查点临时文件指向该文件系统,验证 WAL 提交后出现真实 ENOSPC 时封闭业务请求、保留日志并在重启时重放;不会填满宿主磁盘。只有明确返回事务冲突时才回滚并尝试新事务,断线后不自动重试结果未知的写入。
默认负载为 60 秒,仅用于回归验收,不能代替生产长期稳定性验证。可提高 `--seconds` 执行更长验证;测试不等同于真实断电,也不证明任意损坏页可恢复。
通过验收后固定运行镜像,避免后续构建覆盖标签:
```sh
export CHUSQL_IMAGE=$(docker image inspect chusql:local --format '{{.Id}}')
docker compose --profile web up -d --wait --no-build
```
将 `CHUSQL_IMAGE` 的值与源码版本、验收输出、部署配置及备份一起保留。镜像 ID 适用于本地导入的同一镜像;发布到镜像仓库后可改用 `仓库名@sha256:摘要`。更新时重新构建和验收,再切换该变量。
## 备份与恢复
先停止全部服务,确认数据库不再运行,再归档数据卷:
```sh
docker compose --profile web stop
docker compose ps --status running
docker compose run --rm -T --no-deps --entrypoint tar server \
-C /var/lib/chusql -czf - . > chusql-data.tar.gz
docker compose --profile web up -d --wait
```
同时保留当前镜像、部署配置和管理员口令。数据库使用 WAL 和检查点;不要复制运行中的零散数据文件作为一致备份。
恢复时使用新的项目名创建空数据卷,避免旧目录残留文件。先加载与备份匹配的镜像,再执行:
```sh
docker compose -p chusql-restored run --rm -T --no-deps \
--entrypoint tar server -C /var/lib/chusql -xzf - < chusql-data.tar.gz
docker compose -p chusql-restored up -d --wait server
```
原项目必须停止,或先更改宿主端口以避免冲突。使用原账号口令登录并查询业务表,确认恢复结果。离线维护及恢复限制见[系统引导与恢复](bootstrap.md)。
## 更新
更新前完成停机备份并保留原镜像。使用目标版本源码构建候选镜像,验收后切换运行镜像。以下沿用前文创建的受限构建器;网络环境需要的构建参数也应保留:
```sh
docker buildx build --builder chusql-limited --load -t chusql:candidate .
python3 docker/test-runtime.py --image chusql:candidate --seconds 60
export CHUSQL_IMAGE=$(docker image inspect chusql:candidate --format '{{.Id}}')
docker compose --profile web up -d --wait --no-build
docker compose --profile web logs --tail=100
```
命名卷保留数据,启动引导会保留已有口令。当前源码会为页文件增加 `.pages`、`.page-wal` 和可能残留的 `.pages.pending`;备份和恢复须复制整个数据目录,不能只选择 `.db`、`.idx` 或 `wal.log`。不要用旧版本程序直接打开已经升级的数据目录;回退需要原镜像和升级前备份。
+31 -7
View File
@@ -2,12 +2,24 @@
[← 返回 README](../README.md)
**本页导航:** [在线安装](#在线安装) · [安装方式](#安装方式) · [包内安装](#包内安装) · [常用安装选项](#常用安装选项) · [从源码安装](#从源码安装) · [安装完成后](#安装完成后) · [更新](#更新) · [卸载](#卸载) · [发布校验](#发布校验)
本页内容:
发行包已经包含编译后的程序,普通用户安装时无需安装 GHC 或 Rust 工具链。
- [在线安装](#在线安装)
- [安装方式](#安装方式)
- [包内安装](#包内安装)
- [常用安装选项](#常用安装选项)
- [从源码安装](#从源码安装)
- [安装完成后](#安装完成后)
- [更新](#更新)
- [卸载](#卸载)
- [发布校验](#发布校验)
本页帮助你安装、更新和卸载 ChuSQL。发行包包含编译后的程序,使用发行包时无需安装 Haskell 编译器 GHC 或 Rust 工具链。
项目仓库:[GitHub](https://github.com/TheBadRoger/ChuSQL) · [ChuRepo](https://repo.chunana.cn/ChuNa7a/ChuSQL)。两处均可获取源码,按网络情况选择。
使用容器部署时,按 [Docker 部署](docker.md) 创建口令文件并启动 Compose,无需在宿主机安装 GHC 或 Rust。
## 在线安装
Linux / macOS:
@@ -47,7 +59,9 @@ irm https://repo.chunana.cn/ChuNa7a/ChuSQL/raw/branch/main/scripts/install.ps1 -
& "$env:TEMP\install.ps1" -Component web
```
上述命令下载脚本时优先 GitHub,失败后尝试 ChuRepo。脚本安装和更新时也会自动回退:版本查询或发行包下载失败时尝试 ChuRepo;已选定的版本保持不变,校验失败直接报错。两站均无对应发行包时需使用包内安装或源码安装。显式指定其他仓库或归档 URL 时不会回退到官方仓库。
每个平台的首组命令优先从 GitHub 下载脚本,失败后尝试 ChuRepo。安装和更新时,版本查询或发行包下载失败也会尝试 ChuRepo;已选定的版本保持不变,校验失败直接报错。
两站均无对应发行包时,使用包内安装或源码安装。显式指定其他仓库或归档 URL 时,不会回退到官方仓库。
## 安装方式
@@ -67,10 +81,16 @@ ChuSQL 支持三种安装方式:
### Linux 用户服务
Linux 默认注册 `chusql-server.service` 为当前操作系统用户的 systemd 服务,并启用 linger 和开机启动,无需登录即可运行,异常退出后自动重启。需要可用的 systemd 用户会话;若启用 linger 时权限不足,按安装提示执行 `sudo loginctl enable-linger "$(id -un)"` 后重试。数据库管理员 `--user` 与运行服务的操作系统用户是两个独立身份。
Linux 默认将 `chusql-server.service` 注册为当前操作系统用户的 systemd 服务。安装器启用开机启动和 linger(让用户服务在未登录时也能运行),异常退出后服务会自动重启。
安装需要可用的 systemd 用户会话。若启用 linger 时权限不足,按安装提示执行 `sudo loginctl enable-linger "$(id -un)"` 后重试。
数据库管理员 `--user` 与运行服务的操作系统用户是两个独立身份。
`--no-start` 仅跳过本次启动,仍启用开机启动。非 systemd 环境可使用 `--no-service` 跳过服务注册,直接在后台启动进程。
使用以下命令查看服务状态、重启、停止或查看日志:
```sh
systemctl --user status chusql-server.service
systemctl --user restart chusql-server.service
@@ -136,7 +156,7 @@ sh scripts/install.sh --component web --from-source --source-dir .
## 安装完成后
默认安装包含以下主要内容:
安装器按所选组件安装下列程序,并生成配置及数据目录:
- `chusql-server`:数据库服务;
- `csql`:命令行客户端;
@@ -160,10 +180,14 @@ csql-web
重新运行安装脚本,指定相同的安装目录和组件即可更新,也可用 `--version TAG` 选择版本:
```sh
./install.sh --component both --install-dir "$HOME/.local/share/chusql" --version <tag>
./install.sh --component both --install-dir "$HOME/.local/share/chusql" --version TAG
```
Linux 更新会先停止该安装目录中的 Web 和数据库进程,保留已有 `settings.toml`、数据库和管理员口令,再引导检查系统目录。修改数据位置须编辑配置并自行迁移数据;更新时不能用不同的 `--data-dir` 覆盖现有配置。安装默认重启数据库服务,Web 管理端需重新运行 `csql-web`。
`TAG` 替换为要安装的发行版本。
Linux 更新会先停止该安装目录中的 Web 和数据库进程,保留已有 `settings.toml`、数据库和管理员口令,再引导检查系统目录。
修改数据位置须编辑配置并自行迁移数据;更新时不能用不同的 `--data-dir` 覆盖现有配置。安装默认重启数据库服务,Web 管理端需重新运行 `csql-web`。
Linux 安装目录中保留 `uninstall.sh`。卸载时指定安装目录,数据目录默认读取现有配置。
+45 -27
View File
@@ -2,13 +2,25 @@
[← 返回 README](../README.md)
**本页导航:** [测试方法与复现](#测试方法与复现) · [实测环境](#实测环境) · [性能测试结果](#性能测试结果) · [事务基准](#事务基准) · [瓶颈与使用范围](#瓶颈与使用范围)
本页内容:
- [测试方法与复现](#测试方法与复现)
- [实测环境](#实测环境)
- [性能测试结果](#性能测试结果)
- [事务基准](#事务基准)
- [瓶颈与使用范围](#瓶颈与使用范围)
本页面向需要复现性能测试或评估瓶颈的开发者。数据来自指定日期的单机样本,不能作为其他版本或生产环境的容量承诺。
常用指标中,p50 表示中位延迟,p95 表示按从小到大排列后覆盖 95% 样本的延迟;吞吐表示每秒完成的请求数。
## 测试方法与复现
`benchmark/` 中的应用名为 `benchmark4csql`。它启动真实 `chusql-server`,通过本机 TCP 使用共享客户端协议,服务端调用 Rust 存储;不以纯内存引擎测量代替端到端性能。每次运行创建独立临时数据库,结束后停止服务并清理数据。任一 SQL、结果校验或并发请求失败会使运行失败。
在 `benchmark/` 中构建优化版本,并在 PATH 中放置 release 存储动态库:
### 构建与运行
先在 `chusql-core/storage/` 执行 `cargo build --release`,生成优化后的存储动态库。再进入 `benchmark/`,在 PowerShell 中构建并运行基准程序:
```powershell
$repoRoot = (Resolve-Path '..').Path
@@ -22,11 +34,17 @@ stack build chusql-core-model chusql-core-engine chusql-interface chusql-server
stack exec -- benchmark4csql 10000 50000
```
首次运行前从 `chusql-core/storage/` 执行 `cargo build --release`。Linux 使用 `LD_LIBRARY_PATH` 指向 release 存储目录。两个参数分别为用户数、订单数,默认 10,000 / 50,000,均需至少 100。
Linux 使用 `LD_LIBRARY_PATH` 指向 release 存储目录。两个参数分别为用户数、订单数,默认 10,000 / 50,000,均需至少 100。
读取场景先预热一次,再采样七次,记录均值、p50、p95、最大延迟与请求数/秒。p95 使用最近秩;七次采样的 p95 等于最大值,只适合比较本次场景,不能作为稳定的生产尾延迟。并发场景使用独立的已认证 TCP 会话,1、4、8 个客户端各执行 100 次点查,同步开始;吞吐以整个并发阶段的墙上耗时计算,连接与登录不计入该阶段。
### 采样与结果校验
加载主表每条 INSERT 包含 500 行。另测逐条持久化提交、200 条 INSERT 的单次事务提交、更新、删除和事务回滚。写入保留正常 WAL、数据刷盘与检查点路径,没有关闭持久化。查询耗时包含 TCP、SQL 解析、语义检查、引擎执行与响应解码;不包含 UI 渲染。读测试主要校验结果行数,加载 COUNT、并发点查值和回滚后值另行校验。
读取场景先预热一次,再采样七次,记录均值、p50、p95、最大延迟与请求数/秒。p95 使用最近秩;七次采样的 p95 等于最大值,只适合比较本次场景,不能作为稳定的生产尾延迟。
并发场景使用独立的已认证 TCP 会话,1、4、8 个客户端各执行 100 次点查,同步开始;吞吐以整个并发阶段的墙上耗时计算,连接与登录不计入该阶段。
加载主表每条 INSERT 包含 500 行。另测逐条持久化提交、200 条 INSERT 的单次事务提交、更新、删除和事务回滚。写入保留正常 WAL、数据刷盘与检查点路径,没有关闭持久化。
查询耗时包含 TCP、SQL 解析、语义检查、引擎执行与响应解码;不包含 UI 渲染。读测试主要校验结果行数,加载 COUNT、并发点查值和回滚后值另行校验。
## 实测环境
@@ -81,14 +99,14 @@ stack exec -- benchmark4csql 10000 50000
这些写入阶段各测量一次,不报告分位数。场景按顺序执行,数据库大小、已打开表数量和索引状态会变化。
| 场景 | 处理行数 | 总耗时 ms | 行/秒 |
| ---------------------------------------------- | -------: | ---------: | --------: |
| 用户加载,500 行/提交 | 10,000 | 762.419 | 13,116.15 |
| 订单加载,500 行/提交 | 50,000 | 3,915.697 | 12,769.12 |
| 512 字节宽行加载,500 行/提交 | 5,000 | 1,084.740 | 4,609.40 |
| writes 逐条持久化 INSERT | 200 | 1,119.0 | 178.73 |
| batch_t 加载,100 行/提交 | 10,000 | 1,309.4 | 7,637.09 |
| items 加载,100 行/提交 | 50,000 | 6,336.8 | 7,890.42 |
| 场景 | 处理行数 | 总耗时 ms | 行/秒 |
| ----------------------------- | -------: | --------: | --------: |
| 用户加载,500 行/提交 | 10,000 | 762.419 | 13,116.15 |
| 订单加载,500 行/提交 | 50,000 | 3,915.697 | 12,769.12 |
| 512 字节宽行加载,500 行/提交 | 5,000 | 1,084.740 | 4,609.40 |
| writes 逐条持久化 INSERT | 200 | 1,119.0 | 178.73 |
| batch_t 加载,100 行/提交 | 10,000 | 1,309.4 | 7,637.09 |
| items 加载,100 行/提交 | 50,000 | 6,336.8 | 7,890.42 |
其他单次耗时:
@@ -111,25 +129,25 @@ stack exec -- benchmark4csql 10000 50000
同日、相同机器和 10,000 用户 / 50,000 订单规模的独立测量,结果校验通过。日志为本机 `logs/repair-txn-performance.log`;以下数据与上面的场景不是同一次采样。
| 指标 | 耗时(ms) |
| --- | ---: |
| 200 次独立持久化 INSERT | 1,134.7 |
| 事务 200 条 INSERT 总计 | 687.355 |
| BEGIN | 269.806 |
| 事务内 200 条 INSERT | 41.129 |
| COMMIT | 376.345 |
| 指标 | 耗时(ms) |
| ----------------------- | ---------: |
| 200 次独立持久化 INSERT | 1,134.7 |
| 事务 200 条 INSERT 总计 | 687.355 |
| BEGIN | 269.806 |
| 事务内 200 条 INSERT | 41.129 |
| COMMIT | 376.345 |
事务写入约 290.97 行/秒。BEGIN 与 COMMIT 合计约占总耗时的 94%;COMMIT 还包含差分、冲突检查和持久化,不能全部归因于快照。提交跳过未修改表,整数键差分使用 `IntMap` / `IntSet`,冲突检查与操作生成复用差分。
## 瓶颈与使用范围
| 观察 | 当前边界与测量方向 |
| --- | --- |
| 整库事务快照成本高 | BEGIN / COMMIT 仍读取整库,需分别测传输、修改表比较与刷盘 |
| 不同外层绑定重复扫描 | 20 个不同绑定的相关 EXISTS 在事务基准运行中均值为 2,135.218 ms;相同绑定缓存不等于半连接或通用物化 |
| 大结果与排序有全量成本 | 扫描包含解码、执行、TCP JSON;完整排序后 LIMIT,可分别评估 Top-K、分页和流式返回 |
| 批量提交摊薄固定开销 | 场景的数据库大小和已打开表数量不同,不能当作严格单变量对照;刷盘优化仍须验证恢复 |
| 读并发低于线性扩展 | 8 客户端吞吐约为 1 客户端的 4.8 倍;未测混合读写或并发写 |
| 样本中的观察 | 解读与后续测量方向 |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| 整库事务快照成本高 | 同一提交点复用最近的快照版本;变化后仍读整库,需分别测缓存命中、传输、修改表比较与刷盘 |
| 不同外层绑定重复扫描 | 20 个不同绑定的相关 EXISTS 在事务基准运行中均值为 2,135.218 ms;该样本反映测量时的执行路径,不能用于判断后续半连接优化的效果 |
| 大结果与排序有全量成本 | 扫描包含解码、执行、TCP JSON;完整排序后 LIMIT,可分别评估 Top-K、分页和流式返回 |
| 批量提交摊薄固定开销 | 场景的数据库大小和已打开表数量不同,不能当作严格单变量对照;刷盘优化仍须验证恢复 |
| 读并发低于线性扩展 | 8 客户端吞吐约为 1 客户端的 4.8 倍;未测混合读写或并发写 |
> [!IMPORTANT]
> 这是单机回环、主要为热缓存、管理员身份的基准样本,不是生产容量承诺。未测远程网络、权限目录规模、冷缓存、内存峰值、崩溃恢复耗时或并发写冲突;七次读采样和单次写测量不足以建立统计置信区间。
+103 -9
View File
@@ -2,14 +2,22 @@
[← 返回 README](../README.md)
**本页导航:** [查询、名称与事务](#查询名称与事务) · [自定义类型 DOMAIN](#自定义类型-domain) · [账号与权限](#账号与权限)
本页内容:
客户端选项与元命令见 [commands.md](commands.md),类型与身份字段分别见 [types.md](types.md) 和 [users-and-roles.md](users-and-roles.md)。
- [查询、名称与事务](#查询名称与事务)
- [自定义类型 DOMAIN](#自定义类型-domain)
- [账号与权限](#账号与权限)
本页供编写 SQL 时查阅,说明查询、事务、自定义类型和权限语法。示例使用的数据库与表须事先存在,明确展示创建步骤的示例除外。
客户端选项与元命令见[命令与选项](commands.md),类型与身份字段分别见[类型说明](types.md)和[用户与角色](users-and-roles.md)。
## 查询、名称与事务
### 表名与列名
数据库、表和列的名称按以下规则解析:
- 表级 DDL 可使用当前库限定名,例如选定 `sales` 后执行 `CREATE TABLE sales.orders (id INT)`;必须先选库,跨库 DDL 会被拒绝。
- 选定数据库后语句里直接写表名即可,例如 `SELECT name FROM orders WHERE orders.id = 1`。
- 表名可以带库前缀(`sales.orders`),列名可以带表限定符(`orders.id`)或库限定符(`sales.orders.id`),这几种写法指向同一张表和同一个列。
@@ -18,13 +26,29 @@
### SELECT 与派生表
SELECT 支持以下查询来源和子句:
- `SELECT` 支持 `WHERE`、`GROUP BY`、`HAVING`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN` 与聚合,以及标量 / `IN` / `EXISTS` 子查询和量词比较(`op ANY` / `op ALL`)。
- 来源可以是派生表:`SELECT d.name FROM (SELECT name FROM users) d`。派生表必须带别名,内层不能引用外层列;输出列取裸列名(内层写 `u.id`,外层用 `d.id` 引用)。
- 投影列可以起别名:`SELECT count(*) AS total FROM orders ORDER BY total`,别名就是结果集的列名。`ORDER BY` 的单独名称优先使用唯一的投影别名,再查来源列;重复的排序别名报歧义错误,来源列可以用限定名消除同名冲突。排序接受从 1 开始的输出列序号(例如 `ORDER BY 2 DESC`),在 `*` 展开后解析,零或越界序号报错。
#### 输出别名与排序
投影是查询最终返回的列或表达式。别名和排序遵循以下规则:
- 输出别名:`SELECT count(*) AS total FROM orders ORDER BY total`,别名就是结果集的列名。
- 名称排序:`ORDER BY` 的单独名称优先使用唯一的输出别名,再查来源列。重复的排序别名报歧义错误,来源列可用限定名消除同名冲突。
- 序号排序:接受从 1 开始的输出列序号,例如 `ORDER BY 2 DESC`。序号在 `*` 展开后解析,零或越界序号报错。
- 排序也支持表达式、函数、聚合与子查询,例如 `ORDER BY age + 1 DESC`。复合表达式中的列名只查来源,不能引用输出别名;排序表达式在 LIMIT 前求值,其他投影在 LIMIT 后求值。NULL 升序在前、降序在后,同键保留原行序。
#### 分组后的过滤
分组筛选与权限检查遵循以下规则:
- `HAVING` 在聚合之后、DISTINCT / ORDER BY / LIMIT 之前过滤,只保留布尔真;支持未投影的聚合、子查询及外层相关列,不能引用投影别名。没有 GROUP BY 时 HAVING 形成一个整体组,空输入也有这个组;当前层未聚合的来源列必须出现在 GROUP BY 中。
- 鉴权覆盖整棵查询树:派生表、HAVING 与排序表达式中的子查询也要求内层表的 SELECT 权限。
以下示例按用户汇总订单,筛选订单数大于 1 的组,再排序并返回前 10 组:
```sql
SELECT user_id, SUM(amount) AS total FROM orders
GROUP BY user_id HAVING COUNT(*) > 1
@@ -35,6 +59,10 @@ ORDER BY SUM(amount) + user_id DESC LIMIT 10;
`SELECT DISTINCT` 按完整输出值组合去重,保留首次出现的行,然后执行排序和 LIMIT。NULL 在去重时视为相同值;等值整数与整值浮点按既有数值语义合并。默认 SELECT 或显式 `SELECT ALL` 保留重复行。
内建 List、Maybe 和 Tuple 可按完整类型的结构值比较、排序、分组和去重,也可用于 JOIN、IN、ANY / ALL 与 MIN / MAX。类型之间不隐式转换,SQL NULL 与 Maybe 的 Nothing 分开处理。排序和哈希规则见 [复合值的比较、排序与分组](types.md#比较排序与分组)。
例如,以下查询分别按用户编号和计算结果去重:
```sql
SELECT DISTINCT user_id FROM orders ORDER BY 1 LIMIT 10;
SELECT DISTINCT user_id + 1 AS next_id FROM orders ORDER BY next_id;
@@ -44,24 +72,34 @@ DISTINCT 的排序项必须出现在输出中;排序可使用输出别名、
### 比较运算符
比较和 NULL 处理规则如下:
- 支持 `=`、`<>`(也可写 `!=`)、`>`、`>=`、`<`、`<=`;`<>` 与 `!=` 是同一个运算符。
- `>`、`>=`、`<`、`<=` 在列上有索引时会改写成索引范围扫描;`>=` / `<=` 是闭区间,端点值本身也在结果里。
- 任一操作数为 `NULL` 时比较结果是 `NULL`,`WHERE` 只保留结果为真的行。
### ANY / ALL
ANY / ALL 将一个值与单列子查询的结果逐项比较:
- `值 op ANY (SELECT 单列 …)` 表示子查询里至少有一行让比较成立;`值 op ALL (…)` 表示每一行都成立。
- `ANY` 的空子查询为假、`ALL` 的空子查询为真;子查询里出现 `NULL` 时按 `AND` / `OR` 的三值规则,条件可能为 `NULL`(`WHERE` 里等同不通过)。
- 量词与运算符组合示例:`<> ALL` 与 `NOT IN` 同义,`<> ANY` 表示至少存在一个不相等的值。
### IN / EXISTS 的执行优化
独立 `IN` / `NOT IN` 过滤,以及单表等值相关 `EXISTS` / `NOT EXISTS` 过滤可使用哈希半连接/反连接:右侧只扫描一次,左侧行序和重复行保留,右侧重复值不会放大输出。复杂条件仍使用查询内物化及相关子查询路径;SQL 写法不变。
半连接和反连接根据右侧是否存在匹配行来筛选左行,只返回左侧列。独立 `IN` / `NOT IN` 过滤,以及单表等值相关 `EXISTS` / `NOT EXISTS` 过滤可使用这种优化。
`NOT EXISTS` 保留没有匹配的行,包括空值键;`NOT IN` 的右侧出现 NULL 时,未匹配的非空键得到 NULL,过滤不通过。右侧为空时,IN 为假、NOT IN 为真,包括左值为 NULL 的情况;右侧非空时,NULL 左值按三值逻辑得到 NULL。表达式和半连接使用相同规则。
优化时右侧只扫描一次,左侧行序和重复行保留,右侧重复值不会放大输出。复杂条件仍使用查询内物化及相关子查询路径;SQL 写法不变。
`NOT EXISTS` 保留没有匹配的行,包括空值键;`NOT IN` 的右侧出现 NULL 时,未匹配的非空键得到 NULL,过滤不通过。
右侧为空时,IN 为假、NOT IN 为真,包括左值为 NULL 的情况;右侧非空时,NULL 左值按三值逻辑得到 NULL。表达式和半连接使用相同规则。
### WITH / CTE
公共表表达式(CTE)用 WITH 为子查询命名,供主查询引用。定义和引用规则如下:
- `WITH 名字 AS (SELECT …), 另一个 AS (SELECT …) SELECT … FROM 名字` 先定义再引用,名字可以带列清单(`WITH t(who) AS (SELECT name FROM users) …`)。
- `WITH` 在解析期就展开成派生表,所以引用处只能当普通表用:没有别名时用 CTE 名当限定名(`WITH t AS (…) SELECT t.name FROM t`)。
- 名字不分大小写;只能引用前面已经定义的 CTE,向前引用会当成普通表名去查(有同名表就用表,没有就报 `unknown table`)。
@@ -70,13 +108,28 @@ DISTINCT 的排序项必须出现在输出中;排序可使用输出别名、
### 事务与保存点
开始事务前先选定数据库。事务和保存点命令如下:
- `BEGIN`(或 `START TRANSACTION`)开始显式事务,`COMMIT` 提交,`ROLLBACK` 回滚。
- `BEGIN [TRANSACTION] ISOLATION LEVEL SERIALIZABLE` 或 `START TRANSACTION ISOLATION LEVEL SERIALIZABLE` 开始带读依赖复查的事务,用于先检查再写入的业务。
- `SAVEPOINT 名字` 在事务里记一个回滚点,`ROLLBACK TO 名字`(可写 `ROLLBACK TO SAVEPOINT 名字`)退回该点但保留它,`RELEASE 名字` 丢掉该点与它之后的回滚点。
#### 事务可见性与限制
事务内的可见性和命令范围如下:
- 事务内只允许数据语句;DDL 与 `USE` 会被拒绝,`SHOW DATABASES` 这类不碰表数据的语句照常可用。
- 事务里的读都看开事务那一刻的快照:别的连接在事务期间提交的改动不会出现在事务里,本会话未提交的改动别的连接也看不见。
- 同一连接同时只能有一个事务,没有嵌套事务;回滚点只在事务内有效,事务结束就没了。开事务前要先选好数据库。
#### 提交与冲突
提交采用以下冲突检查和持久化规则:
- 提交时按行 `id` 写回本事务改动过的行,事务没碰过的行不受影响;没有整数 `id` 的表按整表替换提交。
- 提交前会比对基线:本事务改过的行如果已被别的连接改掉或删掉,提交报 `serialization_failure` 并保留事务,由调用方 `ROLLBACK` 或重试。
- 合并本事务与其他连接的修改后,会在发布前复查最终行集的 CHECK、非空和唯一约束;冲突返回 `serialization_failure`,其他表的修改也不会发布。
- SERIALIZABLE 写事务还会复查所有已执行数据语句涉及表的行集,包括子查询;读过的表发生变化即拒绝提交,因此也防止幻行。该模式按表复查,可能拒绝不同行上的合法并发修改;只读事务可完成原快照读取。回退保存点也会撤销其后的读依赖。
- 提交是持久的:整批改动与一条提交标记一次落盘,提交后进程被强杀也会在重启时重放;提交前崩溃则整批丢弃,不会只落一半。
## 自定义类型 DOMAIN
@@ -98,6 +151,8 @@ SHOW DOMAINS;
名称不分大小写,长度为 1–48 个字符,使用 ASCII 字母、数字、下划线,并遵守 SQL 标识符的首字符规则。类型按工作库隔离:另一个库不会自动获得 `sales` 中的定义。类型名不得与已有类型冲突。
### 删除与嵌套定义
删除前必须解除所有列引用,例如:
```sql
@@ -114,14 +169,37 @@ CREATE DOMAIN product_label AS short_text;
CREATE TABLE labels (label product_label);
```
`product_label` 保留对 `short_text` 的引用,最终仍按 `VARCHAR(32)` 检查。`SHOW DOMAINS` 中它的基础表示为 `domain(short_text,varchar(32))`。同样可以多层复用;被表列或其他 DOMAIN 引用的类型不能删除,需先删除引用者,再从外层到内层删除类型。不存在的基础 DOMAIN、自引用及循环定义会被拒绝。
`product_label` 保留对 `short_text` 的引用,最终仍按 `VARCHAR(32)` 检查。`SHOW DOMAINS` 中它的基础表示为 `domain(short_text,varchar(32))`。
当前不支持 DOMAIN 自身的 `CHECK`、`NOT NULL`、`DEFAULT`;这些约束仍可定义在表列上。创建和删除 DOMAIN 不能在显式事务内执行。
同样可以多层复用;被表列或其他 DOMAIN 引用的类型不能删除,需先删除引用者,再从外层到内层删除类型。不存在的基础 DOMAIN、自引用及循环定义会被拒绝。
### 默认值与检查约束
DOMAIN 可声明默认值、非空和具名或匿名检查条件,也可独立修改:
```sql
CREATE DOMAIN quantity AS INT DEFAULT 1 NOT NULL
CONSTRAINT positive CHECK (VALUE > 0) CHECK (VALUE < 1000);
ALTER DOMAIN quantity SET DEFAULT 2;
ALTER DOMAIN quantity DROP DEFAULT;
ALTER DOMAIN quantity SET NOT NULL;
ALTER DOMAIN quantity DROP NOT NULL;
ALTER DOMAIN quantity ADD CONSTRAINT bounded CHECK (VALUE <= 100);
ALTER DOMAIN quantity DROP CONSTRAINT bounded;
```
`VALUE` 指当前值,不分大小写;CHECK 不允许表列、子查询或聚合,NULL 结果通过。多层领域继承全部检查和非空限制;默认值按列、最近领域、基础领域的顺序选择,仅在省略列时使用。
修改前校验受影响的已有行及默认值,失败不发布;并发写入冲突返回 `serialization_failure`,可重试整条语句。领域 DEFAULT 的修改不重写旧行。
创建、修改和删除 DOMAIN 都要求管理员选定工作库,不能在显式事务中执行。不支持 CASCADE、领域改名或修改基础类型。详细语义与持久化边界见 [类型说明](types.md#domain-约束与变更)。
## 账号与权限
默认管理员账号为 `root`。普通账号创建后默认不拥有数据访问权限,需要管理员通过角色进行授权。
### 创建与修改身份
常用账号命令:
```sql
@@ -143,14 +221,22 @@ ALTER ROLE analyst NOSUPERUSER;
SHOW ROLES;
```
`SHOW GRANTS` 允许登录身份查看自己的有效对象权限,输出 `identity`、`privilege`、`object` 和 `grant_option`。结果包含直接授权、启用角色继承、PUBLIC 默认 CONNECT 和所有权隐含权限;表范围授权按现存表展开,超级用户展示全部业务对象权限。系统对象不列出,不接受 `FOR` 查询他人身份。该命令不要求选中工作库;事务中也读取当前权限目录。
### 查询身份与权限
`SHOW GRANTS` 允许登录身份查看自己的有效对象权限,输出 `identity`、`privilege`、`object` 和 `grant_option`。结果包含直接授权、启用角色继承、PUBLIC 默认 CONNECT 和所有权隐含权限;表范围授权按现存表展开,超级用户展示全部业务对象权限。
系统对象不列出,不接受 `FOR` 查询他人身份。该命令不要求选中工作库;事务中也读取当前权限目录。
`SHOW ROLES` 展示全部身份的编号、名称、登录能力、最高权限标志、启用状态及创建时间,不返回口令。`ALTER USER` 与 `ALTER ROLE` 的属性和口令修改语法通用;同一属性不能重复指定。身份管理仅允许最高权限身份执行。
### 最高权限与身份保护
`SUPERUSER` 不依赖启动参数里的管理员名称(`--user`),也不会通过角色成员关系继承。禁用登录身份会拒绝登录;禁用权限角色会停止贡献继承权限。属性修改会使受影响的既有连接失效,下一次请求返回 `unauthorized` 后关闭。最后一个启用且可以登录的最高权限身份不能删除、禁用、取消登录或取消最高权限。
删除身份同时清理授权和双向成员关系;重建同名身份分配新编号。升级迁移的兼容边界见 [用户与角色](users-and-roles.md#升级兼容)。
### 角色成员与对象授权
常用角色和授权命令:
```sql
@@ -168,6 +254,8 @@ DROP ROLE analyst;
表级权限包括 `SELECT`、`INSERT`、`UPDATE`、`DELETE`;数据库权限包括 `CONNECT`、`CREATE`。`ALL` 按对象类型展开。未限定的表名及 `*` 使用当前数据库,`库名.表名` 和 `库名.*` 可指定其他数据库;系统对象不能通过业务 GRANT 授权。
以下示例调整数据库连接权限,并授予表或库内全部表的数据权限:
```sql
REVOKE CONNECT ON DATABASE sales FROM PUBLIC;
GRANT CONNECT, CREATE ON DATABASE sales TO analyst;
@@ -180,10 +268,16 @@ REVOKE SELECT ON sales.orders FROM reviewer;
最高权限身份拥有完整权限。数据库默认向 PUBLIC 开放 CONNECT,不默认授予 CREATE 或数据权限。具有数据库 CREATE 的身份可建表,并成为新表所有者;表所有者可管理表、字段及索引,并授予该表权限。数据库所有者可删除自己的数据库,创建数据库与管理 DOMAIN 仍要求最高权限。
`GRANT ... WITH GRANT OPTION` 允许再转授;所有者也可授予自身对象的权限。普通授予者可撤销自己的授予,所有者及最高权限身份可撤销对象上的其他授予。撤销时收回相应 grant option,并级联清理失去合法来源的下游授权;仍有独立来源的授权保留。转授权能力随启用的角色继承,循环转授不能独立维持权限。REVOKE 不移除所有者的隐含权限。
### 转授权与撤销
转授权指将自己持有的权限再授予其他身份。`GRANT ... WITH GRANT OPTION` 允许再转授;所有者也可授予自身对象的权限。普通授予者可撤销自己的授予,所有者及最高权限身份可撤销对象上的其他授予。
撤销时收回相应 grant option(转授权能力),并级联清理失去合法来源的下游授权;仍有独立来源的授权保留。转授权能力随启用的角色继承,循环转授不能独立维持权限。REVOKE 不移除所有者的隐含权限。
`REVOKE GRANT OPTION FOR SELECT ON users FROM alice` 只移除指定授权来源的转授权能力,保留 alice 的 SELECT 权限;失去唯一支持来源的下游授权会级联撤销,独立来源继续有效。最高权限身份或对象所有者可撤销全部匹配来源,普通授予者只能撤销自己的来源;所有权自身的权限不受 REVOKE 影响。
### 系统目录管理属性
系统目录访问还受目录管理属性控制,具体能力见 [用户与角色](users-and-roles.md)。
目录管理属性示例(仅 SUPERUSER 可设置):
+80 -8
View File
@@ -2,7 +2,15 @@
[← 返回 README](../README.md)
**本页导航:** [自定义类型 DOMAIN](#自定义类型-domain) · [预定义类型的含义](#预定义类型的含义) · [系统类型目录字段](#系统类型目录字段) · [运行时类型与函数](#运行时类型与函数) · [DOMAIN 约束设计(尚未实现)](#domain-约束设计尚未实现)
本页内容:
- [自定义类型 DOMAIN](#自定义类型-domain)
- [预定义类型的含义](#预定义类型的含义)
- [系统类型目录字段](#系统类型目录字段)
- [运行时类型与函数](#运行时类型与函数)
- [DOMAIN 约束与变更](#domain-约束与变更)
本页帮助你选择列类型、理解类型限制和 DOMAIN 约束。
ChuSQL 支持基础类型、运行时复合类型和按数据库保存的 DOMAIN。DOMAIN 为基础类型命名,便于多个表列复用同一定义。
@@ -32,7 +40,17 @@ DOMAIN 按数据库保存,保留名称并复用基础类型的校验与运算
| TIMESTAMP | 日期与时间 | `timestamp` | 否 |
| BLOB | 二进制数据类型名称 | `blob` | 否 |
上述名称表达类型分类,不表示已经完整复刻 PostgreSQL 的存储和数值行为。当前整数类型共用整数值表示;浮点类型共用浮点值表示;`CHAR(n)` 检查字符长度,不自动补空格。`DECIMAL(p,s)` 要求 `1 <= p <= 308`、`0 <= s <= p`,使用有限浮点表示并按小数位舍入,恰好半值时取偶数;舍入后的绝对值达到 `10^(p-s)` 即报精度溢出。它不能视为精确十进制存储。
### 数值和字符行为
上述名称表达类型分类,不表示已经完整复刻 PostgreSQL 的存储和数值行为。当前整数类型共用整数值表示;浮点类型共用浮点值表示;`CHAR(n)` 检查字符长度,不自动补空格。
`DECIMAL(p,s)` 要求 `1 <= p <= 308`、`0 <= s <= p`,使用有限浮点表示并按小数位舍入,恰好半值时取偶数;舍入后的绝对值达到 `10^(p-s)` 即报精度溢出。它不能视为精确十进制存储。
### 行大小与索引键限制
声明长度之外还有物理容量限制:整行的 JSON 编码须放入一个存储页(默认 4096 字节,包含页头和槽位开销),尚无长行溢出存储。
已建立索引的键在默认配置下最多 128 字节;有效上限为 `min(128, floor((page_size - 11) / (order - 1)) - 12)`,密集旧配置可能仍只支持 64 字节。字符串按 UTF-8 字节计数,不按字符数计数。非索引文本不受这项键长限制,仍须满足整行容量。超页行或超长索引键在写 WAL 前拒绝;为已有列创建索引时也会先检查现有值。
## 系统类型目录字段
@@ -53,6 +71,8 @@ DOMAIN 按数据库保存,保留名称并复用基础类型的校验与运算
## 运行时类型与函数
运行时复合类型包括列表、可选值和元组。以下示例展示声明、构造、读取和更新:
```sql
CREATE TABLE typed_values (
id Int,
@@ -77,23 +97,75 @@ SELECT not(TRUE), length('你好');
| --- | --- |
| 标量 | `Int`、`Double`、`Bool`、`String` |
| 列表 | `[a]` / `List a`;`LIST<a>(...)` 按完整类型校验,不隐式转换元素类型 |
| 可选值 | `Maybe a`;`MAYBE<a>()` 为 Nothing,`MAYBE<a>(value)` 为 Just |
| 可选值 | `Maybe a`;`MAYBE<a>()` 表示无值(Nothing),`MAYBE<a>(value)` 表示有值(Just) |
| 元组 | `(a, b, ...)`;`TUPLE<a, b>(...)` 校验各字段类型 |
| `length` | `String -> Int` |
| `not` | `Bool -> Bool` |
空列表和 Nothing 也有确定类型。SQL 可空列的 `NULL` 与 Maybe 内部的 Nothing 不同,不能互相替代。内建函数名称不区分大小写,NULL 参数传播为 NULL,非空参数仍校验类型。
> [!NOTE]
> 复合类型尚无 SQL Eq / Ord / Hash 能力,不能自动用于比较、排序、分组、极值或索引。Web 可以显示复合值及构造器预览,修改须通过 SQL 控制台。
### 比较、排序与分组
类型能力说明一种类型支持哪些运算。相等比较、排序和哈希分别记为 Eq、Ord 和 Hash;哈希用于 JOIN、GROUP BY 和 DISTINCT 的值分组。内建 Int、Double、Bool、String 提供这些能力,List、Maybe 和 Tuple 根据全部参数类型递归获得能力,DOMAIN 沿用基础类型的能力。
同一完整类型的复合值可以使用比较运算、IN、ANY / ALL、JOIN、ORDER BY、GROUP BY、DISTINCT 和 MIN / MAX。不同类型身份之间不隐式转换,例如 `[Int]` 与 `[Double]` 不能比较,空列表也遵守这一规则。SUM / AVG 仍只接受数值标量。
结构比较使用以下规则:
| 类型 | 升序规则 |
| --- | --- |
| List | 从首个不同元素比较大小;相同前缀下,较短列表在前 |
| Maybe | Nothing 在 Just 前;两个 Just 比较内部值 |
| Tuple | 按字段从左到右比较,取首个不同字段的结果 |
| SQL NULL | 升序在所有非空值之前;参与 SQL 比较时结果仍为 NULL |
以下查询都返回 TRUE:
```sql
SELECT LIST<Int>(2) < LIST<Int>(10);
SELECT LIST<Int>(2) < LIST<Int>(2, 9);
SELECT MAYBE<Int>() = MAYBE<Int>();
```
SQL NULL 与 Maybe 的 Nothing 不同,`MAYBE<Int>() = NULL` 返回 NULL。分组和去重将 SQL NULL 合并为一组,将 Nothing 作为另一个非空值处理。同键排序保留输入行序。
复合值按结构哈希,类型身份和各字段共同参与。有限浮点数的正负零相等,嵌套在列表、可选值或元组中也得到相同哈希。NaN、无穷大和载荷类型失配在读取或表达式边界明确拒绝。
复合值的查询能力不包含索引键编码。运行时类型仍不能声明索引、UNIQUE 或 PRIMARY KEY;索引连接遇到复合键时使用原有扫描或哈希路径。Web 可以显示复合值及构造器预览,修改须通过 SQL 控制台。
### 类型身份与持久化
内建构造器编号固定为 `Int=1`、`Double=2`、`Bool=3`、`String=4`、`List=5`、`Maybe=6`、`Tuple=7`;函数编号固定为 `length=1`、`not=2`。完整 `TypeId` 与逻辑类型、物理表示分开,schema 和值使用版本化编码。未知版本、类型或载荷不匹配、非有限浮点数明确报错。
内建构造器编号固定为 `Int=1`、`Double=2`、`Bool=3`、`String=4`、`List=5`、`Maybe=6`、`Tuple=7`;函数编号固定为 `length=1`、`not=2`。完整类型身份 `TypeId` 与逻辑类型、物理表示分开;表结构(schema)和值使用版本化编码。未知版本、类型或载荷不匹配、非有限浮点数明确报错。
> [!IMPORTANT]
> 旧标量编码可继续读取;包含运行时复合类型的数据不能交给不支持该编码的旧客户端。
## DOMAIN 约束设计(尚未实现)
## DOMAIN 约束与变更
当前 DOMAIN 只提供类型命名,不支持自身 `CHECK`、`NOT NULL`、`DEFAULT`、`ALTER DOMAIN` 或 `CASCADE`。后续约束绑定依赖统一 [bound IR](architecture.md#bound-ir)、对象依赖管理和原子结构事务;不能仅放开语法而绕过写入校验、旧数据验证及 WAL 恢复。
领域约束的继承、默认值和修改规则如下。
### 检查条件
DOMAIN 支持字面量 `DEFAULT`、`NOT NULL` 和多条 `CHECK`;语法见 [SQL 语法](syntax.md#自定义类型-domain)。
`CHECK` 的单值变量 `VALUE` 不分大小写,可使用标量表达式和函数,不能引用表列、聚合或子查询。结果为 TRUE 或 SQL NULL 时通过,为 FALSE 时拒绝;非空由独立的 `NOT NULL` 保证。
### 继承与默认值
嵌套领域继承全部 CHECK 和非空限制。列自己的 DEFAULT 优先,其次取最近领域的 DEFAULT,再向基础领域查找;显式 NULL 不触发默认值。列声明的可空属性不能放宽领域的非空限制。
继承属性在绑定时计算,不固化到表列,因此领域默认值变化会影响后续省略该列的 INSERT;已有行不被重写。建表、加列、改列类型、INSERT 和 UPDATE 都使用这些规则;默认值在声明时也须满足完整领域约束。
### 修改定义与依赖失效
`ALTER DOMAIN` 支持 SET / DROP DEFAULT、SET / DROP NOT NULL、ADD [CONSTRAINT 名称] CHECK 和 DROP CONSTRAINT。未命名 CHECK 分配 `domain_check_1` 起的名称,跳过已有名称。变更发布前验证传递依赖领域、列默认值和已有行;失败保留原定义。
依赖定义的绑定查询与写计划在元数据变化后失效;显式事务提交也会复查修改表及其传递领域依赖,按旧定义暂存的写入不能越过新约束。
### 持久化与并发边界
持久存储在库读门内取得同一提交点的完整快照,引擎完成校验后,存储在库写门内复查库身份和提交点,再通过现有 ReplaceSchema WAL 原子发布领域目录。并发写入或库替换导致 `serialization_failure`,调用方可重新执行;不自动重试。
元数据沿用目录列编码,基础列保存类型、可空性和默认值,具名 CHECK 保存为独立目录列;已有只含基础类型的 DOMAIN 可继续读取。原始管理员存储接口仍不是完整 SQL 类型校验入口。
当前不支持领域改名、修改基础类型、CASCADE、NOT VALID 或在显式事务中执行领域 DDL;这些能力仍依赖后续结构事务与对象依赖管理。
+71 -18
View File
@@ -2,7 +2,17 @@
[← 返回 README](../README.md)
**本页导航:** [root 的含义](#root-的含义) · [默认属性与修改方法](#默认属性与修改方法) · [全部身份及凭据字段](#全部身份及凭据字段) · [本机 sudo 认证](#本机-sudo-认证) · [对象授权](#对象授权) · [属性、授权与会话的关系](#属性授权与会话的关系) · [升级兼容](#升级兼容)
本页内容:
- [root 的含义](#root-的含义)
- [默认属性与修改方法](#默认属性与修改方法)
- [全部身份及凭据字段](#全部身份及凭据字段)
- [本机 sudo 认证](#本机-sudo-认证)
- [对象授权](#对象授权)
- [属性、授权与会话的关系](#属性授权与会话的关系)
- [升级兼容](#升级兼容)
本页帮助管理员理解身份属性、配置本机认证和授予对象权限。SQL 命令示例集中在 [SQL 语法](syntax.md#账号与权限)。
ChuSQL 的用户和角色共用一套身份目录及名称空间。用户是允许登录的角色;`CREATE USER` 与 `CREATE ROLE` 的主要区别是默认登录能力。同名用户和角色不能同时存在。
@@ -34,15 +44,32 @@ ChuSQL 的用户和角色共用一套身份目录及名称空间。用户是允
身份创建与修改语法见 [SQL 语法](syntax.md#账号与权限)。
`ALTER USER` 与 `ALTER ROLE` 的属性、口令修改语法通用。可使用 `LOGIN / NOLOGIN`、`SUPERUSER / NOSUPERUSER`、`ENABLED / DISABLED`、`SYSTEM_CATALOG_MANAGER / NOSYSTEM_CATALOG_MANAGER`、`ALLOW_SUDO_AUTH / NOALLOW_SUDO_AUTH`;同一条语句不能重复指定同一属性。
`ALTER USER` 与 `ALTER ROLE` 的属性、口令修改语法通用。同一条语句不能重复指定同一属性。可修改的属性如下:
SUPERUSER 可设置目录管理属性。该属性默认为 false,不经成员关系继承;只有自身启用且允许登录的身份能使用它。目录管理者可以进入 system 库、查看身份目录、创建普通身份、修改普通身份属性与口令及删除普通用户身份。它不能管理 SUPERUSER、其他目录管理者或启用 sudo 认证的身份,不能授予这三个特权属性,不能执行 `DROP ROLE`,也不会因此获得创建业务数据库、直接修改系统表或读取业务数据的权限。受限身份变更在存储写锁内再次检查目标权限。
| 属性 | 开启 / 关闭 |
| --- | --- |
| 登录能力 | `LOGIN / NOLOGIN` |
| 最高权限 | `SUPERUSER / NOSUPERUSER` |
| 启用状态 | `ENABLED / DISABLED` |
| 受限目录管理 | `SYSTEM_CATALOG_MANAGER / NOSYSTEM_CATALOG_MANAGER` |
| 本机 sudo 认证 | `ALLOW_SUDO_AUTH / NOALLOW_SUDO_AUTH` |
<details>
<summary><strong>身份与凭据的内部字段</strong></summary>
### 受限的目录管理能力
SUPERUSER 可设置目录管理属性。该属性默认为 false,不经成员关系继承;只有自身启用且允许登录的身份能使用它。
目录管理者可以进入 system 库、查看身份目录、创建普通身份、修改普通身份属性与口令及删除普通用户身份。目录管理者受到以下限制:
- 不能管理 SUPERUSER、其他目录管理者或启用 sudo 认证的身份,也不能授予这三个特权属性。
- 不能执行 `DROP ROLE`。
- 不会因此获得创建业务数据库、直接修改系统表或读取业务数据的权限。
受限身份变更在存储写锁内再次检查目标权限。
## 全部身份及凭据字段
本节供维护系统目录和协议时查阅,日常授权可直接阅读[本机 sudo 认证](#本机-sudo-认证)或[对象授权](#对象授权)。
身份元数据保存于隐藏的 `system.__system_identities`;凭据单独保存于 `system.__system_users`。以下字段解释实际持久化内容,不表示都能通过 SQL 修改或查询。
| 身份字段 | 含义 | 管理方式 |
@@ -66,13 +93,15 @@ SUPERUSER 可设置目录管理属性。该属性默认为 false,不经成员
内部账号协议可将身份字段和 `password_hash` 合并为账号对象,但哈希不属于公开的角色展示内容。`SHOW ROLES` 展示全部身份的编号、名称、登录能力、最高权限、启用状态、目录管理属性、sudo 认证开关和创建时间,不返回口令哈希,也不展示全部内部字段。系统表由身份管理接口维护,不应直接修改。
</details>
## 本机 sudo 认证
此入口适用于 Linux、macOS 和 Windows,默认关闭。Unix 有效 UID 0(通常通过 sudo 获得)映射到 `[server] sudo_auth_user`;Windows 提升令牌中的 Administrators 成员映射到同一配置身份。不会使用客户端上报的操作系统用户名、`SUDO_USER`、数据库用户名或角色属性作为操作系统身份凭据。
本机 sudo 认证允许受信任的操作系统管理员免数据库口令登录指定身份,默认关闭。入口面向 Linux、macOS 和 Windows;各平台尚待验收的路径见[当前限制](../.agents/context/known-issues.txt)。
先用数据库 SUPERUSER 配置目标身份,例如:
Unix 有效 UID 0(通常通过 sudo 获得)映射到 `[server] sudo_auth_user`;Windows 提升令牌中的 Administrators 成员映射到同一配置身份。不会使用客户端上报的操作系统用户名、`SUDO_USER`、数据库用户名或角色属性作为操作系统身份凭据。
### 配置身份与映射
先确保目标身份已创建,再用数据库 SUPERUSER 配置,例如:
```sql
ALTER USER local_operator ALLOW_SUDO_AUTH;
@@ -87,25 +116,37 @@ ALTER USER local_operator ALLOW_SUDO_AUTH;
sudo_auth_user = "local_operator"
```
### 提升身份后登录
停止原服务后,以 Unix root 或 Windows 提升后的管理员重启 `chusql-server`,使用受管理员保护的配置文件、数据目录和程序路径。以相同提升身份运行 CLI;Unix 示例:
```sh
sudo csql --config /absolute/path/settings.toml --sudo -e "SELECT 1"
```
Windows 在提升的终端中运行 `csql --config C:\absolute\path\settings.toml --sudo -e "SELECT 1"`。`--sudo` 不读取数据库口令,省略 `--user` 时使用配置映射。显式指定其他账号也不能绕过服务端映射。普通 TCP/CLI 口令登录与 Web 登录保持原有口令认证;远程连接无法请求此入口,CLI 也校验实际连接对端为回环地址。
Windows 在提升的终端中运行 `csql --config C:\absolute\path\settings.toml --sudo -e "SELECT 1"`。`--sudo` 不读取数据库口令,省略 `--user` 时使用配置映射。显式指定其他账号也不能绕过服务端映射。
<details>
<summary><strong>凭据保护、认证协议与异常清理</strong></summary>
普通 TCP/CLI 口令登录与 Web 登录保持原有口令认证;远程连接无法请求此入口,CLI 也校验实际连接对端为回环地址。
服务启动时产生独立随机凭据。Unix 放在 `/var/run/chusql-sudo-<实际端口>/credential`,目录 root 所有且为 0700,文件为 0600;Windows 放在系统目录的 `config\ChuSQL-Sudo-<实际端口>\credential`,使用不继承的 Administrators / SYSTEM 专属 DACL。读取入口检查当前进程提升身份;Unix 还验证文件所有权、权限和类型,拒绝链接,Windows 拒绝重解析路径。凭据不会传输到网络,CLI 使用 HMAC-SHA256 回应绑定连接、账号和服务实例的一次性随机挑战;挑战 60 秒后过期,每次认证尝试都会消费挑战,失败按账号限流。
### 凭据保护与认证协议
服务端在 stderr 记录启用映射和认证结果,不记录凭据或证明。成功登录更新身份目录的 `last_login_at`。`ALTER USER local_operator NOALLOW_SUDO_AUTH`、`DISABLED`、`NOLOGIN`、修改口令或删除身份都会使既有连接在下一次请求时失效。映射配置清空并重启服务后,本机入口关闭。
服务启动时产生独立随机凭据,保存位置和权限如下:
- Unix 放在 `/var/run/chusql-sudo-<实际端口>/credential`,目录 root 所有且为 0700,文件为 0600。
- Windows 放在系统目录的 `config\ChuSQL-Sudo-<实际端口>\credential`,使用不继承的 Administrators / SYSTEM 专属 DACL。
读取入口检查当前进程提升身份;Unix 还验证文件所有权、权限和类型,拒绝链接,Windows 拒绝重解析路径。
凭据不会传输到网络,CLI 使用 HMAC-SHA256 回应绑定连接、账号和服务实例的一次性随机挑战;挑战 60 秒后过期,每次认证尝试都会消费挑战,失败按账号限流。
服务端在 stderr 记录启用映射和认证结果,不记录凭据或证明。
成功登录更新身份目录的 `last_login_at`。`ALTER USER local_operator NOALLOW_SUDO_AUTH`、`DISABLED`、`NOLOGIN`、修改口令或删除身份都会使既有连接在下一次请求时失效。映射配置清空并重启服务后,本机入口关闭。
### 异常退出后的清理
正常停止服务时清理凭据。异常退出可能留下端口专属目录;再次启用会明确报错,不读取或覆盖旧凭据。确认对应服务已停止后,由提升身份清理该目录再启动;不要把凭据复制到普通账号可访问的目录。
</details>
## 对象授权
业务授权绑定数据库编号与表编号,身份与所有者绑定身份编号。删除后重建同名数据库、表或身份会获得新编号,旧授权不会随名称恢复。对象存在性以 catalog 为准;不同数据库的同名表互不共享授权。
@@ -118,6 +159,8 @@ Windows 在提升的终端中运行 `csql --config C:\absolute\path\settings.tom
`ALL` 按对象范围展开。未限定表名与 `*` 绑定当前数据库。数据库所有权不自动赋予其他身份所建表的数据权限;启用的所有者角色可以通过成员关系贡献所有权,最高权限属性仍不继承。创建数据库及管理 DOMAIN 仍要求 SUPERUSER;所有权不能通过 REVOKE 移除,当前没有所有权转移命令。
例如,以下命令撤销默认连接权限,并分别授予数据库和表权限:
```sql
REVOKE CONNECT ON DATABASE sales FROM PUBLIC;
GRANT CONNECT, CREATE ON DATABASE sales TO analyst;
@@ -126,11 +169,21 @@ GRANT SELECT ON sales.* TO analyst;
REVOKE SELECT ON sales.orders FROM reader;
```
### PUBLIC 默认连接权限
PUBLIC 是所有身份共享的虚拟角色,不能新建同名用户或角色。已有同名旧身份仍按真实身份编号解析,删除该身份后才使用虚拟 PUBLIC。撤销 PUBLIC CONNECT 会关闭数据库的默认连接权限,其他显式 CONNECT 或数据库所有权仍有效;重新授予会恢复默认连接权限。
每次授予记录授予者。普通身份须持有有效 grant option 或对象所有权,可撤销自己的授予;所有者和 SUPERUSER 可撤销对象上的其他来源。撤销、删除身份或成员变更会清理失去合法来源的转授权链;其他独立来源保留,循环转授不能维持失去根来源的权限。禁用身份暂停权限贡献,保留所有权及管理员直接授权;失去启用授予者的下游转授权会被清理。
### 转授权的来源与撤销
权限快照位于受保护系统目录,经 WAL 原子保存;普通存储请求不能访问该目录。SQL、TCP 和 Web 使用同一服务端检查;TCP 原始存储入口仅允许 SUPERUSER。查询与授权变更通过共享读写锁排序,建表前持久化创建者声明供重启恢复所有权。事务提交重新检查权限和稳定对象编号,权限撤销或对象替换后不能提交旧事务。
转授权能力(grant option)允许身份将持有的权限授予其他身份。每次授予记录授予者。普通身份须持有有效 grant option 或对象所有权,可撤销自己的授予;所有者和 SUPERUSER 可撤销对象上的其他来源。
撤销、删除身份或成员变更会清理失去合法来源的转授权链;其他独立来源保留,循环转授不能维持失去根来源的权限。禁用身份暂停权限贡献,保留所有权及管理员直接授权;失去启用授予者的下游转授权会被清理。
### 权限持久化与提交复查
权限快照位于受保护系统目录,经 WAL 原子保存;普通存储请求不能访问该目录。SQL、TCP 和 Web 使用同一服务端检查;TCP 原始存储入口仅允许 SUPERUSER。查询与授权变更通过共享读写锁排序,建表前持久化创建者声明供重启恢复所有权。
事务提交重新检查权限和稳定对象编号,权限撤销或对象替换后不能提交旧事务。
## 属性、授权与会话的关系