release: v0.4.1 — Aria 级联/ALTER/clearAll + 流式查询/派生表 + 正确性加固
CI / test (20.x) (push) Successful in 10m4s
CI / test (22.x) (push) Successful in 10m8s
CI / test (24.x) (push) Successful in 9m55s
CI / test (18.x) (push) Successful in 10m9s

新增:
- AriaEngine 外键级联(CASCADE/SET NULL/RESTRICT)+ clearAll() 重置 API
- 引擎级 alterTable:Aria DROP COLUMN 重写存储行 + schema 持久化
- 流式查询 queryStream / findStream(LSM 惰性扫描不物化)
- FROM 派生表 / 多列 ON 哈希连接 / COUNT(DISTINCT) / NULLS FIRST/LAST
- 普通列别名 + ORDER BY 别名 + 无表查询 + 字符串常量列
- 演示页引擎切换器(Memory/Aria)+ 预设自动重置

修复:
- Aria WAL DROP_TABLE 崩溃恢复(删表复活)+ 恢复后 WAL 截断
- Memory update/delete 索引维护(unique 约束绕过)
- 关联 EXISTS 绑定失效 / HAVING 标量子查询 / INSERT SELECT 位置错位
- 裸布尔列条件(WHERE done / CASE WHEN done)
- Aria $in 重复行 / JOIN 主表 WHERE 下推 / DROP INDEX 报错
- ORDER BY/GROUP BY/SELECT 表前缀列 + SQL '' 标准转义

质量:894 测试 · 47 套件 · 81.5% 覆盖率
This commit is contained in:
thzxx
2026-08-08 13:36:34 +08:00
parent 82ad8e93b9
commit a1e4f5071c
38 changed files with 13493 additions and 8756 deletions
+1010 -141
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+101 -16
View File
@@ -83,6 +83,8 @@ interface OrderBy {
column: string;
/** 排序方向 */
direction: SortDirection;
/** v0.4.0: NULL 值排序位置(first 排最前 / last 排最后,默认同引擎行为) */
nulls?: 'first' | 'last';
}
/** 查询计划 — 由 Executor 编译 AST 后生成 */
interface QueryPlan {
@@ -116,7 +118,7 @@ interface MetonaPlugin {
/** 销毁 */
destroy(): void;
}
declare const VERSION = "0.3.2";
declare const VERSION = "0.4.1";
/**
* metona-sqlark Plugin — 插件系统
@@ -145,11 +147,6 @@ declare class PluginManager {
destroy(): void;
}
/**
* metona-sqlark Engine Interface — 存储引擎抽象接口
* @module engine/interface
*/
interface IStorageEngine {
/** 引擎名称 */
readonly name: string;
@@ -173,6 +170,8 @@ interface IStorageEngine {
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
/** 查询行 */
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询 — 逐行回调扫描(有 where/limit/projection,无 orderBy 语义;有 orderBy 时实现可回退物化) */
findStream?(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
/** 更新行,返回影响行数 */
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
/** 删除行,返回影响行数 */
@@ -181,6 +180,10 @@ interface IStorageEngine {
count(tableName: string, query?: QueryPlan): Promise<number>;
/** 清空表数据(保留结构) */
clear(tableName: string): Promise<void>;
/** v0.4.1: ALTER TABLE(可选)— 引擎级结构变更(Aria 需重写存储行,其余引擎走 Executor 通用路径) */
alterTable?(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
/** 创建二级索引(CREATE INDEX */
createIndex?(tableName: string, column: string, unique?: boolean): Promise<void>;
/** 删除二级索引(DROP INDEX */
@@ -281,6 +284,8 @@ interface SelectStatement {
columns: ColumnRef[];
distinct?: boolean;
from: string;
/** v0.4.0: FROM (SELECT ...) 派生表(存在时 from 为占位,行源取此子查询结果) */
fromSubquery?: SelectStatement | SelectUnionStatement;
/** 主表别名 */
alias?: string;
/** JOIN 子句列表 */
@@ -364,7 +369,13 @@ declare class QueryExecutor {
private executeJoinSelect;
private prefixRow;
/**
* 哈希连接(v0.3.2):ON 为单一等值条件且右表列为索引/主键时,
* v0.4.1: 提取可下推的 WHERE 条件 — 主表别名前缀的普通条件(如 o.user_id = '1')。
* 下推到引擎可走二级索引;$col/$subquery/$and/$or/$not 等复杂条件保守不下推。
*/
private extractPushableWhere;
/**
* 哈希连接(v0.3.2 单等值 / v0.4.0 多列等值):
* ON 为等值条件(单列或多列)且右表任一列为索引/主键时,
* 收集左表连接值 → 一次 $in 查询右表 → 哈希映射匹配。
* 替代嵌套循环,大表 INNER/LEFT JOIN 复杂度 O(N + M)。
* 不适用时返回 null(回退嵌套循环)。
@@ -389,11 +400,17 @@ declare class QueryExecutor {
private executeRollback;
/** 列列表是否包含 CASE WHEN 表达式 */
private hasCaseColumn;
/**
* v0.3.3: ORDER BY 是否引用 SELECT 别名(如 `SELECT name AS n ... ORDER BY n`)。
* 别名列在引擎层投影前不存在,需投影后重新排序。
*/
private orderByUsesSelectAlias;
/** WHERE 是否包含 CASE WHEN 表达式键 */
private whereHasCase;
getEngine(): IStorageEngine;
/**
* 列投影(v0.3.1):普通列走 projectColumnsCASE WHEN 表达式逐行求值
* 列投影(v0.3.1):普通列走 projectColumnsCASE WHEN 表达式逐行求值
* v0.3.3: 支持 `col AS alias` 列别名
*/
private projectRow;
/** 检查 SELECT 列列表中是否包含聚合函数 */
@@ -516,6 +533,13 @@ declare class Table<T = Record<string, unknown>> {
insert(row: T & Record<string, unknown>): Promise<string>;
insertMany(rows: (T & Record<string, unknown>)[]): Promise<string[]>;
select(columns?: string[]): SelectQueryBuilder;
/** v0.4.0: 流式查询 — 逐行回调,不物化全部结果 */
stream(onRow: (row: T & Record<string, unknown>) => void, query?: {
where?: Record<string, unknown>;
limit?: number;
offset?: number;
columns?: string[];
}): Promise<number>;
update(updates: Partial<T> & Record<string, unknown>): UpdateQueryBuilder;
delete(): DeleteQueryBuilder;
count(where?: Record<string, unknown>): Promise<number>;
@@ -580,6 +604,23 @@ declare class MetonaSqlark {
getTableNames(): Promise<string[]>;
/** 执行 SQL 字符串查询 */
query(sql: string): Promise<unknown>;
/**
* 流式查询:逐行回调,不一次性物化全部结果(大表友好)。
* 支持简单 SELECTWHERE/LIMIT/OFFSET/列投影);
* JOIN/GROUP BY/UNION/聚合/ORDER BY 自动回退为物化查询后逐行回调。
*
* @example
* ```ts
* let total = 0;
* await db.queryStream('SELECT * FROM logs WHERE level = \'error\'', (row) => {
* total++;
* processRow(row);
* });
* ```
*/
queryStream<T extends Record<string, unknown> = Record<string, unknown>>(sql: string, onRow: (row: T) => void): Promise<number>;
/** 流式查询用:剥离主表别名前缀(复用 query 路径的规范化逻辑) */
private normalizeWhereForStream;
/** 执行事务 */
transaction<T>(fn: (trx: Transaction) => Promise<T>): Promise<T>;
/** 导出表数据为 JSON */
@@ -648,6 +689,8 @@ declare class MemoryEngine implements IStorageEngine {
getTableSchema(tableName: string): Promise<TableSchema | null>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询 — 逐行回调(单次迭代,不物化结果数组) */
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
delete(tableName: string, query: QueryPlan): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
@@ -669,6 +712,8 @@ declare class MemoryEngine implements IStorageEngine {
private tryIndexLookup;
/** 更新索引 */
private updateIndexes;
/** v0.3.3: 从所有索引中移除一行的条目(update/delete 前调用,修复索引过期/残留) */
private removeIndexEntries;
/**
* 级联删除:查找引用 tableName.pkValue 的所有表的行并删除。
* @returns 级联删除的行数
@@ -706,6 +751,8 @@ declare class IndexedDBEngine implements IStorageEngine {
getTableSchema(tableName: string): Promise<TableSchema | null>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询 — IDB 批量读入后逐行回调(保持接口一致性) */
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
delete(tableName: string, query: QueryPlan): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
@@ -753,6 +800,8 @@ declare class OPFSEngine implements IStorageEngine {
getTableSchema(tableName: string): Promise<TableSchema | null>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询(委托内存缓存) */
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
delete(tableName: string, query: QueryPlan): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
@@ -798,13 +847,6 @@ interface AriaEngineConfig {
maxMemoryMB?: number;
}
/**
* AriaEngine — 自研页面式存储引擎主类
* @module engine/aria/index
*
* v0.2.5: WAL 同步修复 + MVCC 接入 + 版本统一 + 生产加固
*/
declare class AriaEngine implements IStorageEngine {
readonly name = "aria";
private config;
@@ -826,6 +868,11 @@ declare class AriaEngine implements IStorageEngine {
constructor(config?: AriaEngineConfig);
open(dbName: string, _version: number): Promise<void>;
close(): Promise<void>;
/**
* v0.4.1: 重置数据库 — 清空全部数据与表结构(演示页刷新/重新初始化用)。
* 清空存储后端、LSM、WAL、MVCC 与二级索引,后续可继续使用本实例。
*/
clearAll(): Promise<void>;
isOpen(): boolean;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
@@ -836,8 +883,32 @@ declare class AriaEngine implements IStorageEngine {
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
delete(tableName: string, query: QueryPlan): Promise<number>;
/**
* v0.4.1: 外键级联规则 — 对齐 MemoryEngine.cascadeDelete 行为。
* 删除 tableName 主键为 pkValue 的行前,检查引用它的所有表:
* - RESTRICT: 存在引用行 → 抛 FOREIGN_KEY_VIOLATION
* - CASCADE: 递归删除引用行(含索引/WAL)
* - SET NULL: 引用行外键列置 null(含索引/WAL)
* @returns 级联影响的行数(CASCADE 删除行数 + SET NULL 更新行数)
*/
private applyForeignKeyRules;
/**
* v0.4.0: 流式查询 — 逐行回调,不物化结果数组。
* 全表路径走 LSM rangeScanLazy 惰性扫描;索引等值/范围路径复用 tryIndexLookup。
* 事务中回退物化(快照合并需要全量行集)。
*/
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
clear(tableName: string): Promise<void>;
/**
* v0.4.1: ALTER TABLE — 结构变更真正生效于存储:
* - ADD: 持久化 schemapersistSchemas),行无需修改
* - DROP: 持久化 schema + 遍历主 LSM 重写所有行(移除该列键)+ WAL UPDATE 记录
* (通用路径 getTableSchema 返回副本,Executor 的引用修改对 Aria 无效)
*/
alterTable(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
createIndex(tableName: string, column: string, unique?: boolean): Promise<void>;
dropIndex(tableName: string, column: string, _indexName?: string): Promise<void>;
beginTransaction(): Promise<void>;
@@ -849,6 +920,11 @@ declare class AriaEngine implements IStorageEngine {
releaseSavepoint(name: string): Promise<void>;
backup(): Promise<Record<string, Record<string, unknown>[]>>;
private getAllRows;
/**
* v0.3.3: 将事务未提交快照的变更合并到行列表(新增/更新/删除标记)。
* 幂等操作:行已是最新时不重复修改。
*/
private mergeTxnSnapshot;
private getPK;
private validateRow;
private checkType;
@@ -864,6 +940,13 @@ declare class AriaEngine implements IStorageEngine {
*/
private createSSTableStore;
private applyWALRecord;
/**
* v0.3.3: DROP_TABLE 恢复 — 删除 schema 并清除主 LSM 中该表的所有残留数据。
*
* 此前 DROP_TABLE 在恢复时被忽略,而 CREATE_TABLE 回放会重建 schema
* 导致崩溃后"已删除的表和数据复活"(实证 P0 bug)。
*/
private applyDropTableRecovery;
/** 更新行的二级索引条目 */
private updateSecondaryIndexes;
/** 通过二级索引快速查找 */
@@ -942,6 +1025,8 @@ declare class HybridEngine implements IStorageEngine {
getTableSchema(tableName: string): Promise<TableSchema | null>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询(内存引擎逐行回调) */
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
delete(tableName: string, query: QueryPlan): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
@@ -1131,7 +1216,7 @@ declare class OPFSBackend implements IStorageBackend {
/**
* metona-sqlark — 入口文件
* @module metona-sqlark
* @version 0.2.5
* @version 0.4.1
*
* 前端关系型数据库,内存与磁盘双模式。
* 支持 Query Builder 链式 API 和 SQL 字符串查询。
+1010 -141
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1010 -141
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long