修复错误,优化性能,实现类型系统,优化安装卸载过程
No files matched your search
@@ -1,72 +1,111 @@
|
||||
# 架构与组件
|
||||
|
||||
ChuSQL 采用客户端 / 服务端架构。数据库文件仅由 `chusql-server` 直接访问,其他组件通过网络接口与数据库服务通信。
|
||||
ChuSQL 采用客户端 / 服务端架构。正常运行时,只有数据库服务直接访问数据文件;离线初始化与恢复由 bootstrap 完成。
|
||||
|
||||
## 主要组件
|
||||
## 组件与访问路径
|
||||
|
||||
| 组件 | 作用 |
|
||||
| --- | --- |
|
||||
| `chusql-server` | 数据库服务,负责 SQL 执行、权限检查、事务与数据访问 |
|
||||
| `chusql-web` | Web 管理端,通过数据库服务访问数据 |
|
||||
| `csql` | 命令行客户端,通过数据库服务执行 SQL |
|
||||
| `chusql-core/engine` | SQL 解析、语义检查、优化与执行 |
|
||||
| `chusql-core/storage` | 页面、索引、日志与持久化存储 |
|
||||
|
||||
## 数据流
|
||||
|
||||
典型访问路径为:
|
||||
|
||||
```text
|
||||
~~~text
|
||||
Web / CLI / 其他客户端
|
||||
↓
|
||||
TCP
|
||||
↓
|
||||
chusql-server
|
||||
↓
|
||||
SQL 引擎与存储层
|
||||
↓
|
||||
磁盘
|
||||
```
|
||||
↓ TCP
|
||||
chusql-server
|
||||
↓
|
||||
SQL 引擎 → Rust 存储层 → 磁盘
|
||||
~~~
|
||||
|
||||
所有 SQL 和权限检查均由数据库服务统一处理。Web 和 CLI 不直接访问数据目录,因此多个客户端可以同时连接同一数据库服务。
|
||||
| 组件 | 职责 |
|
||||
| --- | --- |
|
||||
| chusql-server | 登录、会话、鉴权、SQL 执行和事务 |
|
||||
| chusql-web | Web 管理界面,通过 TCP 访问服务 |
|
||||
| csql | 命令行客户端,通过 TCP 执行 SQL |
|
||||
| chusql-core/engine | 解析、语义检查、优化及执行 |
|
||||
| chusql-core/storage | catalog、页面、B+ 树、缓存、WAL 与持久化 |
|
||||
| csql-bootstrap | 安装引导及离线系统目录修复、恢复、重置 |
|
||||
|
||||
服务默认监听 127.0.0.1:7777,Web 默认监听 127.0.0.1:7778。配置位置和平台数据目录见 [config.md](config.md)。
|
||||
|
||||
## 数据目录
|
||||
|
||||
数据保存在 `[storage] data_dir` 指定的位置,主要包含:
|
||||
[storage] data_dir 是持久化数据的根目录,诊断日志由 log_files 单独指定。
|
||||
|
||||
| 目录 | 内容 |
|
||||
| 位置 | 内容 |
|
||||
| --- | --- |
|
||||
| `system/` | 系统数据库,包括账号和权限等系统信息 |
|
||||
| `databases/<数据库名>/` | 用户创建的数据库 |
|
||||
| system/ | 身份、凭据、角色、授权和预定义类型目录 |
|
||||
| databases/<数据库名>/ | 业务数据库的 catalog、数据与索引 |
|
||||
| WAL 与检查点文件 | 已提交写入的恢复记录及刷盘进度 |
|
||||
|
||||
安装完成后只有系统数据库,不自动创建业务数据库。业务数据库需通过 `CREATE DATABASE` 创建。
|
||||
安装只初始化系统数据库,业务数据库由用户创建。表存在性以 catalog 为准,不能仅凭数据文件判断。
|
||||
|
||||
## 数据库服务
|
||||
## 会话与并发
|
||||
|
||||
`chusql-server` 默认监听 `127.0.0.1:7777`,客户端建立连接后完成登录、数据库选择和 SQL 执行。
|
||||
- 每条 TCP 连接拥有独立会话,当前数据库和事务互不影响。
|
||||
- 读请求可并行,写请求由存储层协调。
|
||||
- Web 与 CLI 共享服务端的权限和执行规则,不直接读写数据目录。
|
||||
|
||||
每条连接拥有独立会话状态,例如当前数据库选择不会影响其他连接。不同连接可以同时执行语句:读请求之间可以并行,写请求之间由存储层排队。
|
||||
## SQL 查询处理
|
||||
|
||||
语句里的库前缀和表限定符由引擎在语义层解析:存储层只接受精确的表名键,因此 `FROM sales.orders` 与选定 `sales` 后的 `FROM orders` 指向同一张表。FROM 的来源是一棵树:单表、派生表(括号里的子查询,必须带别名)与连接共用同一个 `FromClause`,所以 `JOIN` 两侧都能是派生表。解析派生表时先把内层语句在空作用域里解析成一张“表”——内层不能引用外层列,输出列统一改成裸列名(内层 `u.id` 在外层用 `d.id` 引用),重名直接报错;执行侧对应一个 `Derived` 算子,把内层计划的结果按别名加前缀。派生表不参与连接顺序重排(叶子的行数未知),投影也不下推进去,但内层计划照常做谓词下推与索引改写。投影项的 `AS` 别名与 `SELECT` 的输出列名一起在语义层解析,派生表与最外层查询都按别名暴露列名。
|
||||
查询依次经过解析、基于 schema 的语义检查、优化和执行。库名、表名、列名及别名在语义层解析;存储层使用精确的表名键。
|
||||
|
||||
`WITH` 不新增语法树节点:解析器在读到一个 `WITH` 时就把每个 CTE 的名字与语句记进一张环境表,遇到 `FROM` 里出现的 CTE 名(不分大小写,只认前面已定义的)就换成 M11 的派生表节点,主语句与 CTE 内部都能再写 `WITH`。这样展开出来的语句对语义层、执行器与鉴权完全等价于手写派生表:CTE 用列清单改名走派生表的投影别名,来源树与权限覆盖不必另做一套。代价是 CTE 不做物化(引用两次就执行两次)、不支持递归,且前向引用会当成普通表名去查。
|
||||
| 能力 | 实现与边界 |
|
||||
| --- | --- |
|
||||
| 派生表 | 内层独立解析,必须有别名,不能引用外层列;输出列按别名暴露 |
|
||||
| WITH / CTE | 解析时展开为派生表;不支持递归或物化,多次引用分别执行 |
|
||||
| ANY / ALL | 按 OR / AND 的三值逻辑求值;空集的 ANY 为假、ALL 为真 |
|
||||
| 子查询复用 | Filter 和计算列按外层绑定分组;JOIN 条件按首次遇到的绑定惰性复用 |
|
||||
| 成本估算 | 使用行数、不同值数和数值列的 16 桶等宽直方图 |
|
||||
| 索引选择 | 优化器判断适用条件与成本,存储层管理实际索引 |
|
||||
|
||||
量词比较走另一个真节点 `QuantCmp`:解析时普通比较运算符先读最长符号,发现后面是 `ANY` / `ALL` 就让给量词解析,因此 `age > ANY (SELECT …)` 与 `age > 25` 共用同一层优先级。求值时先把子查询的行取成字面量,再摊成 `AND`(`ALL`)/ `OR`(`ANY`)链交给既有三值逻辑,于是空子查询 `ANY` 为假、`ALL` 为真,子查询里的 `NULL` 按三值规则传播。节点不带取反位(本方言没有前缀 `NOT`),取反写相反的运算符,如 `<> ALL` 与 `NOT IN` 同义。
|
||||
派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。JOIN 条件的子查询在首次遇到外层绑定时求值,缓存仅限当前连接;空行对不触发求值,普通条件仍逐行计算。尚无半连接或通用物化。统计信息只影响计划选择,优化不能改变查询结果语义。
|
||||
|
||||
优化器的成本估算用存储维护的统计:行数、列级不同值数,以及数值列的列级等宽直方图。直方图是 16 个桶加区间下界 / 上界,只统计 `int` 与 `float` 列(NULL 与非数值不进直方图);存储侧随插入累计、随删除递减,值跑出区间就把旧桶按中点并进新区间,删到表空时清空。这些统计随 schema 下沉到引擎,`Cost.rangeRows` 有直方图就按桶与区间的交集估命中行数(端点按闭区间算),没有直方图退回原来的四分之一;`Cost.selectivityOf` 把这个比例交给连接顺序估算与索引改写的成本把关,因此一条覆盖大半张表的范围条件不会再被改写成索引点查。在线协议里每条列统计多出 `lo` / `hi` / `hist` 三个可选字段,缺省即「没有直方图」,老客户端与老字典都能照常读。
|
||||
完整 SQL 写法及限制见 [syntax.md](syntax.md)。
|
||||
|
||||
子查询按外层绑定分组求值:`Filter` 与计算列两个算子入口先看条件里有没有子查询,有就按子查询引用的外层列取值给扫描到的行分组,同一绑定只把子查询换一次(`IN` / `EXISTS` / 标量 / 量词四种位置共用),然后各行的条件与表达式都用换好的那份求值。非相关子查询没有外层列,因此整条语句只跑一次子查询;相关子查询按不同绑定各跑一次(外层 3 行但绑定只有 2 种就只跑 2 次)。没有子查询的行仍走原来的逐行求值,JOIN 条件与更深的表达式位置不做分组。
|
||||
## 快照事务
|
||||
|
||||
显式事务在会话层实现:`BEGIN` 把当前数据库整库读成内存快照,事务内的语句都在快照上执行;`SAVEPOINT` 把当前快照压进会话里的保存点栈,`ROLLBACK TO` 退回该点并保留它,`RELEASE` 丢掉该点与其后的保存点;`COMMIT` 先把快照、基线与库里现值按行 `id` 逐键比对,检测到写写冲突就报 `serialization_failure` 并保留事务;没有冲突才把差分用一条 `apply_transaction` 请求交给存储层应用;`ROLLBACK` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
||||
| 阶段 | 行为 |
|
||||
| --- | --- |
|
||||
| BEGIN | 将当前数据库读为会话内存快照 |
|
||||
| 事务内执行 | 读取快照与自身修改,看不到其他连接后续提交 |
|
||||
| SAVEPOINT | 保存当前快照;回滚可退回指定保存点 |
|
||||
| COMMIT | 比对基线与现值,确认无写写冲突后原子应用差分 |
|
||||
| ROLLBACK | 丢弃会话快照与保存点 |
|
||||
|
||||
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
||||
有整数 id 的表按行比对并提交差分;其他表按整表替换提交。发生冲突时返回 serialization_failure 并保留事务,由调用方回滚或重试。事务内不支持 DDL 或切换数据库。
|
||||
|
||||
堆页的空间回收分两级:删行只在槽位目录里把该槽的长度置零(墓碑),页内留下的空洞按四分之一阈值判定——死空间达到整页四分之一时随这次删除一起整理(同一次写页,不额外写 WAL),把活行重新码放到页尾并原地更新槽里的偏移。`compact` 请求在没有隐藏列的表上就是逐页做同一件事,只有带隐藏列(`DROP COLUMN` 留下的旧字节)时才整表重写。整理只改页内偏移、不动槽位号,所以索引里存的「页号加槽号」位置继续有效,不需要重建索引;跨页的空洞不做填充,行仍按插入顺序进页,没有 `ORDER BY` 时的可见顺序不变。
|
||||
## WAL 与崩溃恢复
|
||||
|
||||
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、带 option 的授权、成员四张表(`__system_roles` / `__system_grants` / `__system_grant_options` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。带 `WITH GRANT OPTION` 的授权记在第四张表 `__system_grant_options` 里(同样的角色、权限、对象三列,另开一张表省掉给 `__system_grants` 加列带来的系统表结构迁移):普通身份只要手里那条授权带 grant option 就能把同一项权限转授给别的角色,`REVOKE` 仍然只允许管理员,收回授权时连它的 grant option 一起收掉,但不级联收回别人已经转授出去的授权。授权命令由权限服务在会话层之外直接执行,`GRANT` 命令会先按当前库把对象补全成 `库.表` 再落盘,因此判断转授权资格与实际写入用的是同一个对象名。
|
||||
1. 一组写操作与提交标记写入 WAL,每条记录带递增 LSN。
|
||||
2. 数据和 catalog 刷盘后,检查点推进到已完成提交的 LSN。
|
||||
3. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
|
||||
4. 重启时只重放检查点之后的已提交组,丢弃未提交组,并进行幂等检查。
|
||||
|
||||
账号与权限改动会断开受影响账号的在用连接。TCP 服务维护一张在线连接表(自增编号对应一条连接条目:登录账号与「已被断开」标记),会话在执行完 `CREATE` / `ALTER` / `DROP USER` 以及 `CREATE` / `DROP ROLE`、`GRANT` / `REVOKE` 之后算出受影响账号——角色类命令先沿成员关系把角色展开成具体账号名(删角色必须在命令落地前算,落地后成员关系就没了)——再给这些账号的连接打上断开标记。连接线程读到请求后先看标记,命中就回 `unauthorized`(`session was dropped by an account change`)并收工,因此空闲连接在客户端下一次请求时结束,不强行从别的线程中断阻塞读取:Windows 上 GHC 经 `socketToHandle` 得到的句柄,`hGetLine` / `hIsEOF` 阻塞期间既收不到 `throwTo` 也不受 `timeout` 约束,标记法不依赖异步异常,全平台行为一致。权限判定本身不缓存、每条语句现查,所以这里补的是「已登录的长连接不能继续用旧身份」,与不相关账号的连接无关。Web 侧走令牌,令牌登录时记下账号版本,改口令或删账号会递增版本,旧令牌因此自动失效。
|
||||
建表、删表、建索引、删索引和数据修改均进入 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。
|
||||
|
||||
服务监听地址、端口和请求限制可在 `chusql.toml` 的 `[server]` 分区中配置。
|
||||
若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见 [bootstrap.md](bootstrap.md)。
|
||||
|
||||
## 页面与空间回收
|
||||
|
||||
- 删除行先将槽位标记为墓碑。
|
||||
- 死空间达到页大小的四分之一时,随删除整理页内活行。
|
||||
- 整理保留槽位编号,索引中的页号与槽号仍有效。
|
||||
- compact 通常逐页整理;存在 DROP COLUMN 留下的隐藏列时整表重写。
|
||||
|
||||
不进行跨页填洞;没有 ORDER BY 时仍保留当前扫描顺序,但应用应使用显式排序获得稳定结果。
|
||||
|
||||
## 身份、授权与系统目录
|
||||
|
||||
系统表统一使用 __system_ 前缀,身份与凭据目录对普通语句隐藏。SUPERUSER 拥有最高权限,system_catalog_manager 提供受限目录管理能力;两者不通过角色成员关系继承。
|
||||
|
||||
| 机制 | 行为 |
|
||||
| --- | --- |
|
||||
| 角色继承 | 沿成员关系取得权限并集;拒绝自继承与环 |
|
||||
| 表级授权 | 支持 SELECT、INSERT、UPDATE、DELETE 和 ALL |
|
||||
| 转授权 | WITH GRANT OPTION 允许将持有的权限授予其他身份 |
|
||||
| 撤销 | 管理员撤销权限及对应转授权能力;当前不级联撤销已转授权限 |
|
||||
| 对象名称 | 授权前补全为库.表,检查与持久化使用同一名称 |
|
||||
|
||||
类型目录说明见 [types.md](types.md),身份字段与权限边界见 [users-and-roles.md](users-and-roles.md)。
|
||||
|
||||
## 权限变更与会话失效
|
||||
|
||||
账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员关系,确定涉及的登录身份。
|
||||
|
||||
TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不被强行中断。权限逐条查询,避免继续使用旧授权。Web 会话还通过账号版本校验,口令或身份变更使旧凭据失效。
|
||||
@@ -0,0 +1,7 @@
|
||||
# README 徽章
|
||||
|
||||
所有徽章都是本地 SVG,采用统一的深色标签、彩色值区和圆角样式。
|
||||
|
||||
执行 `pwsh -File scripts/update-code-lines.ps1` 可重新生成全部徽章。代码行数包含代码、测试、注释和空行,排除 Git 忽略的构建工件;行数与提交编号是生成时的本地快照。
|
||||
|
||||
发布徽章链接 GitHub 版本列表。构建、测试和门禁徽章提供实时状态页面入口,本地图片显示 `view status` 或 `manual status`,不保存或宣称实时通过状态。门禁由所有者手动触发。
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="24" viewBox="0 0 160 24" role="img" aria-label="build: view status">
|
||||
<title>Open the build workflow for live status; this local badge is a navigation link.</title>
|
||||
<defs><clipPath id="rounded"><rect width="160" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="160" height="24" fill="#1e293b"/><rect x="59" width="101" height="24" fill="#0369a1"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="29.5" y="16">build</text><text x="109.5" y="16">view status</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 634 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="139" height="24" viewBox="0 0 139 24" role="img" aria-label="commit: fd5f320">
|
||||
<title>Local HEAD snapshot when the badges were generated; open commit history for latest updates.</title>
|
||||
<defs><clipPath id="rounded"><rect width="139" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="139" height="24" fill="#1e293b"/><rect x="66" width="73" height="24" fill="#475569"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="33" y="16">commit</text><text x="102.5" y="16">fd5f320</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="167" height="24" viewBox="0 0 167 24" role="img" aria-label="gate: manual status">
|
||||
<title>Open the owner-triggered gate workflow for status; this local badge is a navigation link.</title>
|
||||
<defs><clipPath id="rounded"><rect width="167" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="167" height="24" fill="#1e293b"/><rect x="52" width="115" height="24" fill="#b45309"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="26" y="16">gate</text><text x="109.5" y="16">manual status</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="209" height="24" viewBox="0 0 209 24" role="img" aria-label="languages: Haskell + Rust">
|
||||
<title>ChuSQL implementation languages: Haskell and Rust.</title>
|
||||
<defs><clipPath id="rounded"><rect width="209" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="209" height="24" fill="#1e293b"/><rect x="87" width="122" height="24" fill="#6d28d9"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="43.5" y="16">languages</text><text x="148" y="16">Haskell + Rust</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 617 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="174" height="24" viewBox="0 0 174 24" role="img" aria-label="source lines: 37,149">
|
||||
<title>Physical source lines; includes tests, comments and blank lines.</title>
|
||||
<defs><clipPath id="rounded"><rect width="174" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="174" height="24" fill="#1e293b"/><rect x="108" width="66" height="24" fill="#0369a1"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="54" y="16">source lines</text><text x="141" y="16">37,149</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="139" height="24" viewBox="0 0 139 24" role="img" aria-label="release: browse">
|
||||
<title>Open GitHub releases to view published versions.</title>
|
||||
<defs><clipPath id="rounded"><rect width="139" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="139" height="24" fill="#1e293b"/><rect x="73" width="66" height="24" fill="#6d28d9"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="36.5" y="16">release</text><text x="106" y="16">browse</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 594 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="24" viewBox="0 0 160 24" role="img" aria-label="tests: view status">
|
||||
<title>Open the test workflow for live status; this local badge is a navigation link.</title>
|
||||
<defs><clipPath id="rounded"><rect width="160" height="24" rx="5"/></clipPath></defs>
|
||||
<g clip-path="url(#rounded)"><rect width="160" height="24" fill="#1e293b"/><rect x="59" width="101" height="24" fill="#0369a1"/></g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11"><text x="29.5" y="16">tests</text><text x="109.5" y="16">view status</text></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 633 B |
@@ -0,0 +1,41 @@
|
||||
# Bootstrap 安装引导与离线修复
|
||||
|
||||
`csql-bootstrap` 初始化系统目录,也能在服务无法启动时离线修复。所有维护命令必须先停止使用同一数据目录的 server、Web、CLI 和其他进程。新版本使用数据目录锁拒绝并发维护;旧版本进程不具备该锁,仍须手动停止。
|
||||
|
||||
## 安装引导
|
||||
|
||||
```text
|
||||
csql-bootstrap --config settings.toml --user root --password-stdin
|
||||
```
|
||||
|
||||
口令从标准输入的一行读取,哈希后保存。程序创建系统目录、启用且可登录的 SUPERUSER,以及现有内置类型登记项。重复引导保留已有口令和身份属性,拒绝冲突的类型定义及已降权的引导身份。安装脚本调用此入口。只有明确指定 `--passwordless` 才选择免密初始化。
|
||||
|
||||
## 三种维护策略
|
||||
|
||||
| 命令 | 行为 | 数据影响 |
|
||||
| --- | --- | --- |
|
||||
| `repair` | 修复已知系统表的目录结构、列定义及结构元数据 | 不重放 WAL,不补初始化数据;原有 `.db` 文件必须保持字节一致 |
|
||||
| `recover` | 修复结构、重放可用的已提交 WAL,补齐必要初始化内容 | 可能改变系统表数据;尽量保留现有身份、口令与授权 |
|
||||
| `reset` | 重建初始系统目录,创建指定管理员及内置类型登记项 | 清除原系统目录中的身份、口令、角色关系、授权及其他系统内容 |
|
||||
|
||||
`recovery` 是 `recover` 的兼容别名。三个命令均保留业务数据库目录;`reset` 后业务数据仍在,但原访问授权已清除,需要重新创建身份并授权。
|
||||
|
||||
```text
|
||||
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`。
|
||||
|
||||
维护入口直接使用存储库,不依赖数据库服务能够启动,不提供 SQL 或 TCP 远程维护接口。运行时输出、错误和服务启动修复提示均使用英文。
|
||||
|
||||
## 副本、备份与恢复边界
|
||||
|
||||
程序在数据目录下的 `bootstrap-work-<时间戳>/system` 副本中执行并验证。验证失败时不替换原目录;候选副本保留供检查。成功时将原系统目录移为 `system-backup-<时间戳>`,再发布新目录。输出给出备份与候选路径。备份包含口令哈希和授权,应按数据库数据同等保护。
|
||||
|
||||
发布第二次目录移动失败时会尝试回滚。若发布期间进程或机器崩溃,先停止所有进程,检查输出及上述目录;保存候选与当前目录后,可将完整的备份恢复为数据目录下的 `system`。不要把不同版本或不同时间点的零散文件混合覆盖。
|
||||
|
||||
WAL 只能恢复尚且可用的已提交记录,不能保证恢复已截断、丢失或损坏的历史。已知缺失表的记录数无法恢复完整时会拒绝发布。损坏的数据页也可能使 `recover` 失败,此时需要完整备份或选择清除系统内容的 `reset`。
|
||||
|
||||
目录元数据严重损坏时,只能依据程序内置定义重建已知系统表。存在无法识别结构的系统表文件时拒绝重建,应从备份恢复或使用 `reset`。身份目录计数器损坏后只能依据现存身份编号重建,无法还原已删除身份的历史编号上限。业务数据库自身的结构、数据页和 WAL 不由这些命令修复。
|
||||
@@ -2,14 +2,31 @@
|
||||
|
||||
ChuSQL 的主要用户命令如下:
|
||||
|
||||
类型说明见 [自定义类型与预定义类型](types.md),身份属性说明见 [用户、角色与 root 属性](users-and-roles.md)。
|
||||
|
||||
SQL 的查询、事务、类型、用户与授权语法见 [syntax.md](syntax.md)。
|
||||
|
||||
## 组件命令
|
||||
|
||||
安装脚本在启动服务前运行 `csql-bootstrap`:创建系统目录、初始化默认 `root`(LOGIN / SUPERUSER / ENABLED),再登记内置类型。管理员名称可通过安装选项修改。初始化失败时安装报错,停止启动服务。
|
||||
|
||||
预装目录 `system.__system_types` 登记 18 个名称与别名:INT、INTEGER、BIGINT、SMALLINT、VARCHAR、CHAR、TEXT、STR、BOOLEAN、BOOL、FLOAT、REAL、DOUBLE、DECIMAL、NUMERIC、DATE、TIMESTAMP、BLOB;记录基础表示及是否接受类型参数。该目录为受保护的系统元数据,不创建预定义 DOMAIN;DOMAIN 仍按工作库保存。
|
||||
|
||||
重复引导保留已有身份、密码及一致的类型记录,补齐缺失类型;同名定义冲突明确报错。已有引导身份失去管理员属性时拒绝继续,不自动提升权限。
|
||||
|
||||
修复或补齐安装引导时,可停止数据库服务后运行 `csql-bootstrap --config FILE --user root --password-stdin`,通过标准输入提供口令。
|
||||
|
||||
系统目录故障时,先停止所有数据库进程,再依次考虑 `csql-bootstrap repair --config FILE`、`recover` 或清除系统身份和授权的 `reset`。`repair` 仅修复结构,涉及数据修改时中止;完整说明见 [bootstrap.md](bootstrap.md)。
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `chusql-server` | 启动数据库服务 |
|
||||
| `chusql-web` | 启动 Web 管理端 |
|
||||
| `csql` | 启动命令行客户端 |
|
||||
| `csql-web` | 按配置启动 Web 管理端 |
|
||||
| `csql-bootstrap` | 初始化系统身份与内置类型目录 |
|
||||
|
||||
各组件默认使用同一份 `chusql.toml`。需要临时使用其他配置时,可通过 `--config FILE` 指定。
|
||||
各组件默认使用同一份 `settings.toml`。需要临时使用其他配置时,可通过 `--config FILE` 指定。
|
||||
|
||||
## csql
|
||||
|
||||
@@ -31,6 +48,8 @@ csql [options]
|
||||
| `--config FILE` | 指定配置文件 |
|
||||
| `-h, --help` | 显示帮助 |
|
||||
|
||||
CLI 历史只记录完整 SQL 和元命令。包含 `IDENTIFIED` 或 `PASSWORD`(忽略大小写)的输入不记录,多行口令输入和未完成语句也不会逐行写入历史;包含这些文本的普通字符串或标识符同样会被排除。已有历史文件不会自动清理。含这些文本的 SQL 解析错误会隐藏输入原文。
|
||||
|
||||
示例:
|
||||
|
||||
```sh
|
||||
@@ -40,53 +59,8 @@ csql -f json -e "SELECT count(*) FROM orders"
|
||||
|
||||
在脚本里非交互使用(配合 `-e`):
|
||||
|
||||
- 口令从标准输入读取,行尾用 LF(`\n`)或者干脆不写行尾;CRLF 会把 `\r` 算进口令,结果是 `unauthorized`。
|
||||
- `root's password: ` 这个提示写在标准输出里,解析输出时要先去掉它;错误信息写在标准错误,失败时退出码为 `1`。
|
||||
|
||||
表名与列名的写法:
|
||||
|
||||
- 选定数据库后语句里直接写表名即可,例如 `SELECT name FROM orders WHERE orders.id = 1`。
|
||||
- 表名可以带库前缀(`sales.orders`),列名可以带表限定符(`orders.id`)或库限定符(`sales.orders.id`),这几种写法指向同一张表和同一个列。
|
||||
- 只有候选唯一时才自动推导前缀;同名的表或列出现在多个候选上会以 `ambiguous table` / `ambiguous column` 报错,需要写全。
|
||||
- `FROM orders AS o` 之后只能用 `o.id`,不能再写 `orders.id`。
|
||||
|
||||
查询写法:
|
||||
|
||||
- `SELECT` 支持 `WHERE`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN`、`GROUP BY` 与聚合,以及标量 / `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` 仍按来源列解析,要引用派生表的列得写限定名(如 `ORDER BY d.total`)。
|
||||
- 鉴权覆盖整棵来源树:查派生表时内层表也要有权限,只有外层引用的表有权限不够。
|
||||
|
||||
比较运算符:
|
||||
|
||||
- 支持 `=`、`<>`(也可写 `!=`)、`>`、`>=`、`<`、`<=`;`<>` 与 `!=` 是同一个运算符。
|
||||
- `>`、`>=`、`<`、`<=` 在列上有索引时会改写成索引范围扫描;`>=` / `<=` 是闭区间,端点值本身也在结果里。
|
||||
- 任一操作数为 `NULL` 时比较结果是 `NULL`,`WHERE` 只保留结果为真的行。
|
||||
|
||||
量词比较:
|
||||
|
||||
- `值 op ANY (SELECT 单列 …)` 表示子查询里至少有一行让比较成立;`值 op ALL (…)` 表示每一行都成立。
|
||||
- `ANY` 的空子查询为假、`ALL` 的空子查询为真;子查询里出现 `NULL` 时按 `AND` / `OR` 的三值规则,条件可能为 `NULL`(`WHERE` 里等同不通过)。
|
||||
- 取反写相反的运算符即可:`<> ALL` 与 `NOT IN` 同义,`<> ANY` 表示不存在相等的行。
|
||||
|
||||
公共表表达式:
|
||||
|
||||
- `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`)。
|
||||
- 不支持递归 CTE,主语句与每个 CTE 都只能是 `SELECT`;同一个 CTE 被引用两次会各执行一次(不做物化)。
|
||||
- 鉴权同样覆盖 CTE 里的表:`WITH d AS (SELECT name FROM orders) …` 要求对 `orders` 有权限。
|
||||
|
||||
事务:
|
||||
|
||||
- `BEGIN`(或 `START TRANSACTION`)开始显式事务,`COMMIT` 提交,`ROLLBACK` 回滚。
|
||||
- `SAVEPOINT 名字` 在事务里记一个回滚点,`ROLLBACK TO 名字`(可写 `ROLLBACK TO SAVEPOINT 名字`)退回该点但保留它,`RELEASE 名字` 丢掉该点与它之后的回滚点。
|
||||
- 事务内只允许数据语句;DDL 与 `USE` 会被拒绝,`SHOW DATABASES` 这类不碰表数据的语句照常可用。
|
||||
- 事务里的读都看开事务那一刻的快照:别的连接在事务期间提交的改动不会出现在事务里,本会话未提交的改动别的连接也看不见。
|
||||
- 同一连接同时只能有一个事务,没有嵌套事务;回滚点只在事务内有效,事务结束就没了。开事务前要先选好数据库。
|
||||
- 提交时按行 `id` 写回本事务改动过的行,事务没碰过的行不受影响;没有整数 `id` 的表按整表替换提交。
|
||||
- 提交前会比对基线:本事务改过的行如果已被别的连接改掉或删掉,提交报 `serialization_failure` 并保留事务,由调用方 `ROLLBACK` 或重试。
|
||||
- 提交是持久的:整批改动与一条提交标记一次落盘,提交后进程被强杀也会在重启时重放;提交前崩溃则整批丢弃,不会只落一半。
|
||||
- 口令从标准输入读取,兼容 LF、CRLF 和无行尾;保留口令中的空格。
|
||||
- 管道输入时口令提示和错误信息写在标准错误,标准输出保留查询结果,失败时退出码为 `1`。
|
||||
|
||||
### 交互模式
|
||||
|
||||
@@ -105,43 +79,6 @@ SQL 语句以分号结束,可跨多行输入。
|
||||
| `\f <格式>` | 修改输出格式 |
|
||||
| `\dr` | 查看角色与授权信息(管理员) |
|
||||
|
||||
## 账号与权限
|
||||
|
||||
默认管理员账号为 `root`。普通账号创建后默认不拥有数据访问权限,需要管理员通过角色进行授权。
|
||||
|
||||
常用账号命令:
|
||||
|
||||
```sql
|
||||
CREATE USER alice IDENTIFIED BY 'password';
|
||||
ALTER USER alice IDENTIFIED BY 'new-password';
|
||||
DROP USER alice;
|
||||
```
|
||||
|
||||
常用角色和授权命令:
|
||||
|
||||
```sql
|
||||
CREATE ROLE analyst;
|
||||
CREATE ROLE reviewer;
|
||||
GRANT SELECT ON orders TO analyst;
|
||||
GRANT SELECT ON orders TO reviewer WITH GRANT OPTION;
|
||||
GRANT analyst TO reviewer;
|
||||
GRANT reviewer TO alice;
|
||||
REVOKE SELECT ON orders FROM analyst;
|
||||
REVOKE analyst FROM reviewer;
|
||||
REVOKE reviewer FROM alice;
|
||||
DROP ROLE analyst;
|
||||
```
|
||||
|
||||
当前表级权限包括:`SELECT`、`INSERT`、`UPDATE`、`DELETE` 和 `ALL`。
|
||||
|
||||
角色可以互相继承:`GRANT analyst TO reviewer` 让 `reviewer` 拿到 `analyst` 的授权,并把 `analyst` 记为 `reviewer` 的成员;继承会沿成员关系传递,成环的授权被拒绝(报 `conflict`),自继承同样拒绝。删角色时它的两向成员关系一起清掉。被授权者是不是角色,看名字是否在 `__system_roles` 里;用户名与角色名同名时按角色算。
|
||||
|
||||
管理员拥有完整权限;数据库、表、索引等结构管理操作仅允许管理员执行。
|
||||
|
||||
`GRANT ... WITH GRANT OPTION` 让拿到的授权可以再转授:普通身份只有手里那条授权带 grant option 时才能执行 `GRANT`,报错是 `grant option required: <权限> ON <表>`;`REVOKE` 仍然只允许管理员,收回授权会连它的 grant option 一起收掉,且不会级联收回别人转授出去的授权。转授权随角色继承一起传递。
|
||||
|
||||
系统数据库 `system` 仅允许管理员访问。
|
||||
|
||||
## chusql-web
|
||||
|
||||
基本用法:
|
||||
@@ -154,14 +91,13 @@ chusql-web [options]
|
||||
|
||||
| 选项 | 说明 |
|
||||
| --- | --- |
|
||||
| `--host H` | 监听地址 |
|
||||
| `--port N` | 监听端口 |
|
||||
| `--static DIR` | 静态资源目录 |
|
||||
| `--listen-host H` | 监听地址 |
|
||||
| `--user NAME` | 管理员账号名 |
|
||||
| `--config FILE` | 指定配置文件 |
|
||||
| `-h, --help` | 显示帮助 |
|
||||
|
||||
其余请求大小、会话、登录限制、分页等参数通常通过 `chusql.toml` 配置即可。
|
||||
静态资源目录固定为 `static`。其余请求大小、会话、登录限制、分页等参数通常通过 `settings.toml` 配置即可。
|
||||
|
||||
## chusql-server
|
||||
|
||||
@@ -175,8 +111,9 @@ chusql-server [options]
|
||||
|
||||
| 选项 | 说明 |
|
||||
| --- | --- |
|
||||
| `--host H` | 数据库服务监听地址 |
|
||||
| `--port N` | 数据库服务监听端口 |
|
||||
| `--listen-host H` | 数据库服务监听地址 |
|
||||
| `--user NAME` | 管理员账号名 |
|
||||
| `--max-message N` | 单条请求大小上限 |
|
||||
| `--max-rows N` | 单次查询返回行数上限 |
|
||||
| `--config FILE` | 指定配置文件 |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 配置选项
|
||||
|
||||
ChuSQL 使用统一配置文件 `chusql.toml`。默认位置:
|
||||
ChuSQL 使用统一配置文件 `settings.toml`。默认位置:
|
||||
|
||||
| 平台 | 路径 |
|
||||
| --- | --- |
|
||||
| Windows | `%APPDATA%\ChuSQL\chusql.toml` |
|
||||
| Linux / macOS | `$XDG_CONFIG_HOME/ChuSQL/chusql.toml`;未设置时为 `~/.config/ChuSQL/chusql.toml` |
|
||||
| Windows | `%APPDATA%\ChuSQL\settings.toml` |
|
||||
| Linux / macOS | `$XDG_CONFIG_HOME/ChuSQL/settings.toml`;未设置时为 `~/.config/ChuSQL/settings.toml` |
|
||||
|
||||
`chusql-server`、`chusql-web` 和 `csql` 均可通过 `--config <path>` 临时指定其他配置文件。配置优先级为:**命令行参数 > 配置文件 > 内置默认值**。
|
||||
|
||||
@@ -17,18 +17,17 @@ ChuSQL 使用统一配置文件 `chusql.toml`。默认位置:
|
||||
| `[btree] order` | `4` | B+ 树阶数 |
|
||||
| `[buffer] pool_size` | `1024` | 缓冲池页数 |
|
||||
| `[storage] data_dir` | 平台默认数据目录 | 数据文件保存位置 |
|
||||
| `[storage] log_files` | 安装时填写平台日志目录 | 诊断日志目录,不是 WAL |
|
||||
| `[log] level` | `info` | 日志级别 |
|
||||
|
||||
数据目录仅由 `chusql-server` 访问。生产环境建议使用绝对路径。
|
||||
正常运行时数据目录由 `chusql-server` 访问,离线初始化与修复由 `csql-bootstrap` 访问。安装器填写绝对路径,日志目录由启动器与安装脚本使用。
|
||||
|
||||
## Web 配置
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `host` | `127.0.0.1` | Web 服务监听地址 |
|
||||
| `port` | `7778` | Web 服务端口 |
|
||||
| `static_dir` | `static` | 静态资源目录 |
|
||||
| `user` | `root` | 管理员账号名 |
|
||||
| `listen_host` | `127.0.0.1` | Web 服务监听地址 |
|
||||
| `cookie_secure` | `false` | HTTPS 部署时应启用 |
|
||||
| `body_limit` | `65536` | 请求体大小上限(字节) |
|
||||
| `session_idle` | `28800` | 会话空闲超时(秒) |
|
||||
@@ -43,12 +42,14 @@ ChuSQL 使用统一配置文件 `chusql.toml`。默认位置:
|
||||
| `password_min_length` | `12` | 普通账号最短口令长度 |
|
||||
| `password_classes` | `2` | 口令至少包含的字符类别数 |
|
||||
|
||||
监听地址见上表,静态资源目录固定为 `static`(相对安装目录),管理员账号名不在配置里、由启动参数 `--user` 指定(默认 `root`);后两项不能用配置项改写。
|
||||
|
||||
## Server 配置
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `host` | `127.0.0.1` | 数据库服务监听地址 |
|
||||
| `port` | `7777` | 数据库服务端口 |
|
||||
| `listen_host` | `127.0.0.1` | 数据库服务监听地址 |
|
||||
| `max_message` | `1048576` | 单条请求最大大小(字节) |
|
||||
| `max_rows` | `1000` | 单次查询最大返回行数 |
|
||||
|
||||
@@ -70,7 +71,11 @@ ChuSQL 使用统一配置文件 `chusql.toml`。默认位置:
|
||||
|
||||
配置可通过以下方式修改:
|
||||
|
||||
1. 直接编辑 `chusql.toml`;
|
||||
1. 直接编辑 `settings.toml`;
|
||||
2. 通过 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` 可覆盖数据目录,指定安装目录不会改变默认数据位置。
|
||||
@@ -80,7 +80,7 @@ Windows:
|
||||
- `csql`:命令行客户端;
|
||||
- `chusql-web`:Web 管理端;
|
||||
- `csql-bootstrap`:系统目录初始化工具;
|
||||
- `chusql.toml`:全局配置文件;
|
||||
- `settings.toml`:全局配置文件;
|
||||
- 数据目录与日志目录。
|
||||
|
||||
常用命令:
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# 性能基准
|
||||
|
||||
## 测试方法与复现
|
||||
|
||||
`benchmark/` 中的应用名为 `benchmark4csql`。它启动真实 `chusql-server`,通过本机 TCP 使用共享客户端协议,服务端调用 Rust 存储;不以纯内存引擎测量代替端到端性能。每次运行创建独立临时数据库,结束后停止服务并清理数据。任一 SQL、结果校验或并发请求失败会使运行失败。
|
||||
|
||||
在 `benchmark/` 中构建优化版本,并在 PATH 中放置 release 存储动态库:
|
||||
|
||||
```powershell
|
||||
$env:TMP = 'D:\Workspace\ChuSQL\logs\cli-test-tmp'
|
||||
$env:TEMP = $env:TMP
|
||||
$env:PATH = 'D:\Workspace\ChuSQL\chusql-core\storage\target\release;' + $env:PATH
|
||||
$env:GHC_CHARENC = 'UTF-8'
|
||||
stack build chusql-core-model chusql-core-engine chusql-interface chusql-server chusql-bootstrap benchmark4csql --force-dirty --ghc-options=-O2
|
||||
stack exec -- benchmark4csql 10000 50000
|
||||
```
|
||||
|
||||
首次运行前从 `chusql-core/storage/` 执行 `cargo build --release`,并创建上面的临时目录。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、并发点查值和回滚后值另行校验。
|
||||
|
||||
## 实测环境
|
||||
|
||||
- 测量日期:2026-10-05,Asia/Shanghai。
|
||||
- CPU:AMD Ryzen 7 7840H,8 核 / 16 逻辑处理器。
|
||||
- 可见物理内存:31.2 GiB。
|
||||
- 系统:Windows 11 专业版,10.0.26200。
|
||||
- 数据目录:D 盘 NTFS 上的仓库 `logs/` 临时子目录;未测存储设备裸吞吐。
|
||||
- 编译:GHC 9.10.3,Haskell 本地包 `-O2`;Rust release 动态库。
|
||||
- 规模:10,000 用户、50,000 订单、5,000 条 512 字节字符串宽行;订单按用户循环分配,年龄按 100 个值循环分布。
|
||||
|
||||
## 性能测试结果
|
||||
|
||||
完整运行返回退出码 0,输出 `PRESSURE_OK all result checks passed` 和 `BENCHMARK_OK all scenarios completed`。原始日志保存于本机 `logs/benchmark-performance.log`。下面保留该次成功运行的数据;首次失败运行不参与统计。
|
||||
|
||||
启动、引导、认证及创建基准库合计 0.62 秒。纯 TCP Ping 200 次平均 0.100 ms;schema catalog 200 次平均 0.267 ms。主键点查另外连续执行 100 次,平均 0.300 ms,100/100 命中。
|
||||
|
||||
### 读取场景
|
||||
|
||||
每项七个测量样本,单位为毫秒。请求吞吐由总采样耗时计算,为串行吞吐。
|
||||
|
||||
| 场景 | 返回行数 | 均值 | p50 | p95 / 最大值 | 请求/秒 |
|
||||
| --------------------------------------- | -------: | --------: | --------: | -----------: | -------: |
|
||||
| 用户全表扫描 | 10,000 | 23.768 | 18.961 | 36.309 | 42.07 |
|
||||
| 订单全表扫描 | 50,000 | 114.094 | 118.676 | 131.074 | 8.76 |
|
||||
| 主键点查 | 1 | 0.415 | 0.278 | 0.742 | 2,408.23 |
|
||||
| name 点查,无二级索引 | 1 | 21.776 | 21.908 | 23.257 | 45.92 |
|
||||
| name 点查,有二级索引 | 1 | 0.338 | 0.312 | 0.466 | 2,961.71 |
|
||||
| 主键闭区间范围,100 行 | 100 | 0.675 | 0.644 | 0.985 | 1,482.33 |
|
||||
| 按年龄排序,LIMIT 100 | 100 | 19.361 | 20.748 | 21.720 | 51.65 |
|
||||
| 100 个年龄值分组计数 | 100 | 18.139 | 17.843 | 22.200 | 55.13 |
|
||||
| 用户与订单过滤连接 | 4,500 | 144.929 | 142.598 | 158.723 | 6.90 |
|
||||
| 过滤派生表与订单连接 | 4,500 | 141.797 | 141.535 | 148.438 | 7.05 |
|
||||
| CTE 后按年龄分组 | 100 | 28.539 | 29.146 | 30.921 | 35.04 |
|
||||
| 无关联 COUNT 标量子查询,外层 LIMIT 100 | 100 | 30.777 | 29.844 | 34.692 | 32.49 |
|
||||
| EXISTS,20 个不同外层绑定 | 20 | 2,061.770 | 2,070.403 | 2,123.953 | 0.49 |
|
||||
| 512 字节字符串宽行全表扫描 | 5,000 | 22.969 | 22.791 | 24.485 | 43.54 |
|
||||
|
||||
相关 EXISTS 用派生表先限定外层为前 20 个用户,每次子查询按 `orders.user_id = u.id` 匹配;`orders.user_id` 没有二级索引。每个绑定具有五条订单,外层与内层都读取真实存储。
|
||||
|
||||
### 并发点查
|
||||
|
||||
每客户端 100 次主键点查。单请求延迟合并所有客户端样本计算;总吞吐用并发阶段墙上时间计算,不能用单请求延迟均值的倒数替代。
|
||||
|
||||
| 客户端 | 请求数 | 墙上耗时 ms | 总吞吐 请求/秒 | 单请求均值 ms | p50 ms | p95 ms | 最大 ms |
|
||||
| -----: | -----: | ----------: | -------------: | ------------: | -----: | -----: | ------: |
|
||||
| 1 | 100 | 29.094 | 3,437.10 | 0.290 | 0.250 | 0.544 | 1.183 |
|
||||
| 4 | 400 | 37.862 | 10,564.79 | 0.374 | 0.335 | 0.574 | 1.796 |
|
||||
| 8 | 800 | 48.877 | 16,367.72 | 0.481 | 0.437 | 0.735 | 2.027 |
|
||||
|
||||
### 写入、索引及事务
|
||||
|
||||
这些写入阶段各测量一次,不报告分位数。场景按顺序执行,数据库大小、已打开表数量和索引状态会变化。
|
||||
|
||||
| 场景 | 处理行数 | 总耗时 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 |
|
||||
| writes 事务中 200 条 INSERT,含 BEGIN / COMMIT | 200 | 67,549.539 | 2.96 |
|
||||
| batch_t 加载,100 行/提交 | 10,000 | 1,309.4 | 7,637.09 |
|
||||
| items 加载,100 行/提交 | 50,000 | 6,336.8 | 7,890.42 |
|
||||
|
||||
其他单次耗时:
|
||||
|
||||
| 操作 | 耗时 ms |
|
||||
| -------------------------------- | ------: |
|
||||
| 10,000 行 users 建 name 索引 | 239.7 |
|
||||
| UPDATE writes 的 400 行 | 16.4 |
|
||||
| 事务内 UPDATE writes 的 400 行 | 0.5 |
|
||||
| ROLLBACK 上述修改 | 0.3 |
|
||||
| DELETE writes 的 200 行 | 10.3 |
|
||||
| DELETE users 单行 | 48.1 |
|
||||
| DELETE users 全部剩余行 | 765.7 |
|
||||
| 50,000 行 items 无索引 code 点查 | 135.2 |
|
||||
| 50,000 行 items 建 code 索引 | 1,089.8 |
|
||||
| 建索引后 code 点查 | 0.4 |
|
||||
|
||||
回滚后校验 `writes.id = 1` 的 age 仍为 2,事务中写入的 0 没有落入正式数据。删除后 writes 行数为 200。加载、连接及并发点查的结果校验全部通过。
|
||||
|
||||
## 性能瓶颈分析
|
||||
|
||||
### 1. 事务提交的整库差分是首要排查点
|
||||
|
||||
本次 200 条事务 INSERT 比 200 次独立持久化 INSERT 慢约 60 倍。事务阶段开始时,数据库已有 10,000 用户、50,000 订单及 5,000 宽行,事务只修改 writes 表。事务内一次 400 行 UPDATE 仅耗时 0.5 ms,说明不能将整个事务阶段的 67.55 秒简单归因于 SQL 更新计算或 fsync。
|
||||
|
||||
代码依据:服务端 [Session.hs](../chusql-server/src/ChuSQL/Server/Session.hs) 的 BEGIN 和 COMMIT 都获取整库快照。`transactionDiffs` 对具有整数 id 的表先走 keyedDiff,即使 staged 与 base 完全相同,也会建立关联列表并对每行执行线性 `lookup` / `any`;50,000 行未修改表仍可能进行平方级比较。COMMIT 分别调用冲突检查和操作生成,可能重复计算差分。这是与实测异常最吻合的瓶颈候选。
|
||||
|
||||
当前没有 BEGIN、逐条语句、COMMIT 的独立计时或 CPU profile,不能给各部分分摊比例。下一步应先拆分计时,再对未修改表快速跳过、以 Map / HashMap 做按 id 差分、复用一次差分计算结果,并考虑记录实际修改表与键。整库快照及内存统计重算也需要测量。不能据当前实现假设把 Web 编辑放进一个大事务就会更快。
|
||||
|
||||
### 2. 不同绑定的相关子查询仍重复扫描
|
||||
|
||||
20 个 EXISTS 绑定平均 2.06 秒,而单次 50,000 行订单扫描约 114 ms;两者量级相符。缓存只能合并相同绑定,这里的 20 个用户 id 都不同,因此仍需多次读取订单。执行路径 [Eval.hs](../chusql-core/engine/src/ChuSQL/Core/Engine/Algebra/Eval.hs) 将子查询逐绑定执行,没有半连接,EXISTS 也先得到子查询结果再判断是否为空。
|
||||
|
||||
优先验证 `orders.user_id` 二级索引及基于统计的点查选择,再评估半连接、EXISTS 找到首行即可停止、或单次物化内层匹配键。这个推断有查询结构和耗时量级支持,但本次没有对索引版相关子查询做对照,不把预期收益当作已测结果。
|
||||
|
||||
### 3. 扫描、结果传输与全量排序限制大结果查询
|
||||
|
||||
50,000 行扫描比 10,000 行扫描慢约 4.8 倍。无索引 name 点查只返回一行,耗时仍接近用户全表扫描;索引后均值下降约 64 倍,说明扫描工作量具有决定性影响。宽行加载吞吐降至约 4,609 行/秒,低于短行的约 1.3 万行/秒。
|
||||
|
||||
`sortRows` 使用完整 `sortBy` 后再 LIMIT,返回 100 行并不代表只处理 100 行。当前全表结果会经过存储解码、引擎求值及 TCP JSON 编码。下一步可测 Top-K 排序、分页或流式返回,以及存储、引擎、编码各阶段的时间和分配量。本次没有冷缓存、磁盘裸吞吐、分阶段 CPU 或内存峰值测量,无法把扫描耗时全部归到磁盘。
|
||||
|
||||
### 4. 批量提交有效,读并发扩展低于线性
|
||||
|
||||
批量加载达到约 1.3 万行/秒,200 次独立提交只有约 179 行/秒。数据和已打开表数量不同,这不是严格的单变量对照;但与 WAL 提交、表刷盘和检查点固定成本被摊薄的执行路径一致。Rust 的 `group_via_wal` 还会刷所有已打开表,应单独测量并评估只刷本组实际修改表的收益及恢复正确性。
|
||||
|
||||
8 客户端点查总吞吐约为 1 客户端的 4.8 倍,单请求均值从 0.290 ms 上升至 0.481 ms。表明只读请求有并发收益,也存在共享资源、调度或协议开销。本次没有混合读写或并发写压测,不据此推断写扩展能力;当前每库写独占的限制仍在。
|
||||
|
||||
## 使用范围
|
||||
|
||||
这是单机回环、主要为热缓存、管理员身份的单次基准,不是生产容量承诺。未覆盖远程网络、鉴权角色开销、崩溃恢复耗时、并发写冲突或大型宽表。七次读采样和单次写测量不足以建立统计置信区间,后续优化应使用相同参数、多次独立运行和分阶段 profile 比较。
|
||||
@@ -0,0 +1,155 @@
|
||||
# SQL 语法
|
||||
|
||||
客户端选项与元命令见 [commands.md](commands.md),类型与身份字段分别见 [types.md](types.md) 和 [users-and-roles.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`),这几种写法指向同一张表和同一个列。
|
||||
- 只有候选唯一时才自动推导前缀;同名的表或列出现在多个候选上会以 `ambiguous table` / `ambiguous column` 报错,需要写全。
|
||||
- `FROM orders AS o` 之后只能用 `o.id`,不能再写 `orders.id`。
|
||||
|
||||
### SELECT 与派生表
|
||||
|
||||
- `SELECT` 支持 `WHERE`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN`、`GROUP BY` 与聚合,以及标量 / `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` 优先使用唯一的投影别名,再查来源列;重复的排序别名报歧义错误,来源列可以用限定名消除同名冲突。排序暂不接受任意表达式或列序号。
|
||||
- 鉴权覆盖整棵来源树:查派生表时内层表也要有权限,只有外层引用的表有权限不够。
|
||||
|
||||
### 比较运算符
|
||||
|
||||
- 支持 `=`、`<>`(也可写 `!=`)、`>`、`>=`、`<`、`<=`;`<>` 与 `!=` 是同一个运算符。
|
||||
- `>`、`>=`、`<`、`<=` 在列上有索引时会改写成索引范围扫描;`>=` / `<=` 是闭区间,端点值本身也在结果里。
|
||||
- 任一操作数为 `NULL` 时比较结果是 `NULL`,`WHERE` 只保留结果为真的行。
|
||||
|
||||
### ANY / ALL
|
||||
|
||||
- `值 op ANY (SELECT 单列 …)` 表示子查询里至少有一行让比较成立;`值 op ALL (…)` 表示每一行都成立。
|
||||
- `ANY` 的空子查询为假、`ALL` 的空子查询为真;子查询里出现 `NULL` 时按 `AND` / `OR` 的三值规则,条件可能为 `NULL`(`WHERE` 里等同不通过)。
|
||||
- 量词与运算符组合示例:`<> ALL` 与 `NOT IN` 同义,`<> ANY` 表示至少存在一个不相等的值。
|
||||
|
||||
### WITH / CTE
|
||||
|
||||
- `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`)。
|
||||
- 不支持递归 CTE,主语句与每个 CTE 都只能是 `SELECT`;同一个 CTE 被引用两次会各执行一次(不做物化)。
|
||||
- 鉴权同样覆盖 CTE 里的表:`WITH d AS (SELECT name FROM orders) …` 要求对 `orders` 有权限。
|
||||
|
||||
### 事务与保存点
|
||||
|
||||
- `BEGIN`(或 `START TRANSACTION`)开始显式事务,`COMMIT` 提交,`ROLLBACK` 回滚。
|
||||
- `SAVEPOINT 名字` 在事务里记一个回滚点,`ROLLBACK TO 名字`(可写 `ROLLBACK TO SAVEPOINT 名字`)退回该点但保留它,`RELEASE 名字` 丢掉该点与它之后的回滚点。
|
||||
- 事务内只允许数据语句;DDL 与 `USE` 会被拒绝,`SHOW DATABASES` 这类不碰表数据的语句照常可用。
|
||||
- 事务里的读都看开事务那一刻的快照:别的连接在事务期间提交的改动不会出现在事务里,本会话未提交的改动别的连接也看不见。
|
||||
- 同一连接同时只能有一个事务,没有嵌套事务;回滚点只在事务内有效,事务结束就没了。开事务前要先选好数据库。
|
||||
- 提交时按行 `id` 写回本事务改动过的行,事务没碰过的行不受影响;没有整数 `id` 的表按整表替换提交。
|
||||
- 提交前会比对基线:本事务改过的行如果已被别的连接改掉或删掉,提交报 `serialization_failure` 并保留事务,由调用方 `ROLLBACK` 或重试。
|
||||
- 提交是持久的:整批改动与一条提交标记一次落盘,提交后进程被强杀也会在重启时重放;提交前崩溃则整批丢弃,不会只落一半。
|
||||
|
||||
## 自定义类型 DOMAIN
|
||||
|
||||
由具有 `SUPERUSER` 属性的管理员选定工作库后执行:
|
||||
|
||||
```sql
|
||||
USE sales;
|
||||
CREATE DOMAIN short_label AS VARCHAR(32);
|
||||
CREATE DOMAIN amount AS DECIMAL(10, 2);
|
||||
CREATE TABLE products (id INT, label short_label, price amount);
|
||||
INSERT INTO products (id, label, price) VALUES (1, 'book', 19.95);
|
||||
SHOW DOMAINS;
|
||||
```
|
||||
|
||||
`sales` 需事先存在。`short_label` 复用 `VARCHAR(32)` 的长度检查;`amount` 复用 `DECIMAL(10, 2)` 的数值处理。列保留 DOMAIN 名称,但赋值、比较、运算仍使用基础类型的语义。
|
||||
|
||||
已有表也可在 `ALTER TABLE ... ADD COLUMN` 或 `ALTER TABLE ... ALTER COLUMN ... TYPE` 中使用 DOMAIN。`SHOW DOMAINS` 返回当前库的 `domain`(名称)和 `base_type`(基础类型表示)。
|
||||
|
||||
名称不分大小写,长度为 1–48 个字符,使用 ASCII 字母、数字、下划线,并遵守 SQL 标识符的首字符规则。类型按工作库隔离:另一个库不会自动获得 `sales` 中的定义。类型名不得与已有类型冲突。
|
||||
|
||||
删除前必须解除所有列引用,例如:
|
||||
|
||||
```sql
|
||||
DROP TABLE products;
|
||||
DROP DOMAIN short_label;
|
||||
DROP DOMAIN amount;
|
||||
```
|
||||
|
||||
DOMAIN 可以基于内置基础类型或当前库已有的 DOMAIN 创建,例如:
|
||||
|
||||
```sql
|
||||
CREATE DOMAIN short_text AS VARCHAR(32);
|
||||
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、自引用及循环定义会被拒绝。
|
||||
|
||||
当前不支持 DOMAIN 自身的 `CHECK`、`NOT NULL`、`DEFAULT`;这些约束仍可定义在表列上。创建和删除 DOMAIN 不能在显式事务内执行。
|
||||
|
||||
## 账号与权限
|
||||
|
||||
默认管理员账号为 `root`。普通账号创建后默认不拥有数据访问权限,需要管理员通过角色进行授权。
|
||||
|
||||
常用账号命令:
|
||||
|
||||
```sql
|
||||
CREATE USER alice IDENTIFIED BY 'password';
|
||||
ALTER USER alice IDENTIFIED BY 'new-password';
|
||||
DROP USER alice;
|
||||
```
|
||||
|
||||
用户和角色共用名称空间。`CREATE USER` 默认可以登录,`CREATE ROLE` 默认不能登录;两者默认启用,且不具有最高权限。管理员可以修改身份属性:
|
||||
|
||||
```sql
|
||||
ALTER ROLE analyst LOGIN;
|
||||
ALTER ROLE analyst IDENTIFIED BY 'analyst-password-1';
|
||||
ALTER USER alice NOLOGIN;
|
||||
ALTER USER alice DISABLED;
|
||||
ALTER USER alice ENABLED LOGIN;
|
||||
ALTER ROLE analyst SUPERUSER;
|
||||
ALTER ROLE analyst NOSUPERUSER;
|
||||
SHOW ROLES;
|
||||
```
|
||||
|
||||
`SHOW ROLES` 展示全部身份的编号、名称、登录能力、最高权限标志、启用状态及创建时间,不返回口令。`ALTER USER` 与 `ALTER ROLE` 的属性和口令修改语法通用;同一属性不能重复指定。身份管理仅允许最高权限身份执行。
|
||||
|
||||
`SUPERUSER` 不依赖启动参数里的管理员名称(`--user`),也不会通过角色成员关系继承。禁用登录身份会拒绝登录;禁用权限角色会停止贡献继承权限。属性修改会使受影响的既有连接失效,下一次请求返回 `unauthorized` 后关闭。最后一个启用且可以登录的最高权限身份不能删除、禁用、取消登录或取消最高权限。
|
||||
|
||||
服务启动时迁移旧账号及角色,保留账号编号、口令、时间和原有授权关系。旧用户名与角色名重名时明确拒绝迁移,不合并身份。删除身份会同时清理其授权和两向成员关系;重建同名身份会分配新编号。
|
||||
|
||||
常用角色和授权命令:
|
||||
|
||||
```sql
|
||||
CREATE ROLE analyst;
|
||||
CREATE ROLE reviewer;
|
||||
GRANT SELECT ON orders TO analyst;
|
||||
GRANT SELECT ON orders TO reviewer WITH GRANT OPTION;
|
||||
GRANT analyst TO reviewer;
|
||||
GRANT reviewer TO alice;
|
||||
REVOKE SELECT ON orders FROM analyst;
|
||||
REVOKE analyst FROM reviewer;
|
||||
REVOKE reviewer FROM alice;
|
||||
DROP ROLE analyst;
|
||||
```
|
||||
|
||||
当前表级权限包括:`SELECT`、`INSERT`、`UPDATE`、`DELETE` 和 `ALL`。
|
||||
|
||||
角色可以互相继承:`GRANT analyst TO reviewer` 让 `reviewer` 成为 `analyst` 的成员并取得其授权;继承会沿成员关系传递,成环的授权被拒绝(报 `conflict`),自继承同样拒绝。用户也可以直接接受表级授权或作为其他角色的成员。
|
||||
|
||||
管理员拥有完整权限;数据库、表、索引等结构管理操作仅允许管理员执行。
|
||||
|
||||
`GRANT ... WITH GRANT OPTION` 让拿到的授权可以再转授:普通身份只有手里那条授权带 grant option 时才能执行 `GRANT`,报错是 `grant option required: <权限> ON <表>`;`REVOKE` 仍然只允许管理员,收回授权会连它的 grant option 一起收掉,且不会级联收回别人转授出去的授权。转授权随角色继承一起传递。
|
||||
|
||||
系统目录访问还受目录管理属性控制,具体能力见 [用户与角色](users-and-roles.md)。
|
||||
|
||||
|
||||
目录管理属性示例(仅 SUPERUSER 可设置):
|
||||
|
||||
```sql
|
||||
ALTER USER alice SYSTEM_CATALOG_MANAGER;
|
||||
ALTER USER alice NOSYSTEM_CATALOG_MANAGER;
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
# 自定义类型与预定义类型
|
||||
|
||||
本文描述 ChuSQL 当前实现。预定义类型是引擎已经支持的基础类型;自定义类型使用 `DOMAIN` 给基础类型命名,便于多个表列复用同一类型定义。
|
||||
|
||||
## 自定义类型 DOMAIN
|
||||
|
||||
DOMAIN 按数据库保存,保留名称并复用基础类型的校验与运算语义。可引用内置类型或当前库已有 DOMAIN;被列或其他 DOMAIN 引用时不能删除。
|
||||
|
||||
创建、使用、查询和删除的语法及限制见 [SQL 语法](syntax.md#自定义类型-domain)。
|
||||
|
||||
## 预定义类型的含义
|
||||
|
||||
安装期间,引导程序将下列 18 个名称及别名登记到系统类型目录。大小写不影响识别。
|
||||
|
||||
| SQL 名称 | 含义与默认行为 | 目录中的默认 `base_type` | 接受参数 |
|
||||
| --- | --- | --- | --- |
|
||||
| INT、INTEGER | 整数,两种名称等价 | `int` | 否 |
|
||||
| BIGINT | 大整数类型名称 | `bigint` | 否 |
|
||||
| SMALLINT | 小整数类型名称 | `smallint` | 否 |
|
||||
| VARCHAR | 可变长文本;省略长度时不设声明长度上限 | `str` | 是:`VARCHAR(n)` |
|
||||
| CHAR | 有长度限制的文本,默认长度 1 | `char(1)` | 是:`CHAR(n)` |
|
||||
| TEXT、STR | 文本,两种名称等价 | `str` | 否 |
|
||||
| BOOLEAN、BOOL | 布尔值 `TRUE` / `FALSE` | `bool` | 否 |
|
||||
| FLOAT、REAL | 浮点数,两种名称等价 | `float` | 否 |
|
||||
| DOUBLE | 双精度类型名称 | `double` | 否 |
|
||||
| DECIMAL、NUMERIC | 指定精度和小数位的数值,默认 `(10, 0)` | `decimal(10,0)` | 是:`(p)` 或 `(p,s)` |
|
||||
| DATE | 日期,使用 `YYYY-MM-DD` 格式 | `date` | 否 |
|
||||
| TIMESTAMP | 日期与时间 | `timestamp` | 否 |
|
||||
| BLOB | 二进制数据类型名称 | `blob` | 否 |
|
||||
|
||||
上述名称表达类型分类,不表示已经完整复刻 PostgreSQL 的存储和数值行为。当前整数类型共用整数值表示;浮点类型共用浮点值表示;`CHAR(n)` 检查字符长度,不自动补空格。`DECIMAL(p,s)` 要求 `1 <= p <= 308`、`0 <= s <= p`,使用有限浮点表示并按小数位舍入,恰好半值时取偶数;舍入后的绝对值达到 `10^(p-s)` 即报精度溢出。它不能视为精确十进制存储。
|
||||
|
||||
## 系统类型目录字段
|
||||
|
||||
全局预定义类型目录为 `system.__system_types`,每个名称或别名单独占一条记录。
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `id` | 类型记录的持久化编号 |
|
||||
| `name` | 小写类型名称,例如 `integer` 或 `varchar` |
|
||||
| `base_type` | 不带显式参数时的规范基础表示;例如 `integer` 对应 `int`,`varchar` 对应 `str` |
|
||||
| `parameterized` | 该名称是否接受长度、精度等类型参数;不表示具体列已经指定了参数 |
|
||||
|
||||
例如,目录中的 `varchar` 记录表示支持 `VARCHAR(n)`;实际列声明为 `VARCHAR(32)` 时,长度 32 保存在列结构中,而不是修改全局目录记录。
|
||||
|
||||
该目录是受保护的系统元数据,普通 SQL 不直接展示或修改它。类型执行语义由引擎实现,不能通过手工增加目录行获得新的基础类型。安装登记不会创建任何预定义 DOMAIN;DOMAIN 定义另存于各工作库的 catalog。
|
||||
|
||||
首次安装完成管理员初始化后登记类型。重复引导保留一致的记录并补齐缺失项;同名定义冲突会报错,停止安装启动流程,不覆盖现有定义。
|
||||
@@ -0,0 +1,68 @@
|
||||
# 用户、角色与 root 属性
|
||||
|
||||
ChuSQL 的用户和角色共用一套身份目录及名称空间。用户是允许登录的角色;`CREATE USER` 与 `CREATE ROLE` 的主要区别是默认登录能力。同名用户和角色不能同时存在。
|
||||
|
||||
## root 的含义
|
||||
|
||||
安装阶段默认创建名为 `root` 的管理员身份,并赋予以下属性:
|
||||
|
||||
| 属性 | 初始值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `can_login` | `true` | 可以使用账号及口令登录 |
|
||||
| `is_superuser` | `true` | 具有系统管理及数据访问的最高权限 |
|
||||
| `system_catalog_manager` | `false` | 独立目录管理属性;SUPERUSER 本身已具有有效目录管理能力 |
|
||||
| `enabled` | `true` | 身份处于启用状态 |
|
||||
|
||||
管理员名称可通过安装选项修改。`root` 不是凭名字获得特殊权限:授权判断依据身份属性,其他身份也可被授予 `SUPERUSER`。修改启动参数中的管理员名称不会提升已有普通身份。
|
||||
|
||||
口令由安装脚本通过标准输入交给引导程序,经哈希后保存,不写入配置文件。重复引导保留已有口令和属性;已有引导身份被降权或禁用时明确拒绝继续,不自动恢复最高权限。
|
||||
|
||||
系统保护最后一个同时满足 `enabled=true`、`can_login=true`、`is_superuser=true` 的身份:不能删除、禁用、取消登录能力或取消最高权限。若要替换 root,应先创建另一个可登录且启用的管理员。
|
||||
|
||||
## 默认属性与修改方法
|
||||
|
||||
| 创建方式 | 登录能力 | 最高权限 | 启用状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 安装引导管理员(默认 root) | LOGIN | SUPERUSER | ENABLED |
|
||||
| `CREATE USER` | LOGIN | NOSUPERUSER | ENABLED |
|
||||
| `CREATE ROLE` | NOLOGIN | NOSUPERUSER | ENABLED |
|
||||
|
||||
身份创建与修改语法见 [SQL 语法](syntax.md#账号与权限)。
|
||||
|
||||
`ALTER USER` 与 `ALTER ROLE` 的属性、口令修改语法通用。可使用 `LOGIN / NOLOGIN`、`SUPERUSER / NOSUPERUSER`、`ENABLED / DISABLED`、`SYSTEM_CATALOG_MANAGER / NOSYSTEM_CATALOG_MANAGER`;同一条语句不能重复指定同一属性。
|
||||
|
||||
SUPERUSER 可设置目录管理属性。该属性默认为 false,不经成员关系继承;只有自身启用且允许登录的身份能使用它。目录管理者可以进入 system 库、查看身份目录、创建普通身份、修改普通身份属性与口令及删除普通用户身份。它不能管理 SUPERUSER 或其他目录管理者,不能授予这两个特权属性,不能执行 `DROP ROLE`,也不会因此获得创建业务数据库、直接修改系统表或读取业务数据的权限。受限身份变更在存储写锁内再次检查目标权限。
|
||||
|
||||
## 全部身份及凭据字段
|
||||
|
||||
身份元数据保存于隐藏的 `system.__system_identities`;凭据单独保存于 `system.__system_users`。以下字段解释实际持久化内容,不表示都能通过 SQL 修改或查询。
|
||||
|
||||
| 身份字段 | 含义 | 管理方式 |
|
||||
| --- | --- | --- |
|
||||
| `id` | 身份的稳定编号;删除后不复用,重建同名身份获得新编号 | 系统分配 |
|
||||
| `user` | 用户或角色名称,共用名称空间,不区分大小写 | 创建时指定;当前不支持改名 |
|
||||
| `can_login` | 是否允许直接登录;仅允许登录仍不足以保证登录成功,还需启用及正确口令 | `LOGIN / NOLOGIN` |
|
||||
| `is_superuser` | 是否具有最高权限;不通过角色成员关系继承 | `SUPERUSER / NOSUPERUSER` |
|
||||
| `system_catalog_manager` | 是否具有受限的系统目录管理能力;不继承、不替代数据访问授权 | `SYSTEM_CATALOG_MANAGER / NOSYSTEM_CATALOG_MANAGER`,仅 SUPERUSER 可设置 |
|
||||
| `enabled` | 是否启用身份;禁用后不能登录,也不再贡献角色继承权限 | `ENABLED / DISABLED` |
|
||||
| `identity_version` | 身份目录格式与旧数据迁移标记;旧记录可为 0,当前统一身份格式为 1 | 系统维护;不是会话版本 |
|
||||
| `registered_at` | 身份创建时间;旧记录缺失时由迁移补齐 | 系统记录 |
|
||||
| `last_login_at` | 最近一次成功登录时间;尚未登录时为空 | 成功登录时更新 |
|
||||
| `revision` | 会话校验版本;修改口令或身份属性后递增,使旧登录凭据失效 | 系统维护;不是登录次数 |
|
||||
|
||||
| 凭据字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `id` | 对应身份目录中的编号 |
|
||||
| `password_hash` | 口令哈希,不是明文口令;通过口令修改命令更新 |
|
||||
|
||||
内部账号协议可将身份字段和 `password_hash` 合并为账号对象,但哈希不属于公开的角色展示内容。`SHOW ROLES` 展示全部身份的编号、名称、登录能力、最高权限、启用状态、目录管理属性和创建时间,不返回口令哈希,也不展示全部内部字段。系统表由身份管理接口维护,不应直接修改。
|
||||
|
||||
## 属性、授权与会话的关系
|
||||
|
||||
普通用户需要直接授权或通过角色继承获得数据权限。示例见 [SQL 语法](syntax.md#账号与权限)。
|
||||
|
||||
`NOLOGIN` 只禁止该身份直接登录,角色仍可贡献继承权限;`DISABLED` 会使该角色停止贡献继承权限。`SUPERUSER` 始终由登录身份自身的属性决定,加入一个最高权限角色不会继承最高权限。
|
||||
|
||||
修改身份属性、口令或相关权限会使受影响的既有会话失效;TCP 连接在下一次请求时收到 `unauthorized` 并关闭。删除身份会清理它的授权及双向成员关系;重新创建同名身份不会恢复原有授权。
|
||||
|
||||
当前尚未提供 PostgreSQL 的全部角色选项,例如独立的 `CREATEDB`、`CREATEROLE`、连接数限制或口令到期时间。应以本文列出的属性及当前支持的授权命令为准。
|
||||