release: v0.4.1 — Aria 级联/ALTER/clearAll + 流式查询/派生表 + 正确性加固
新增: - 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:
+85
@@ -202,6 +202,91 @@ export class MetonaSqlark {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 流式查询(v0.4.0) ----
|
||||
|
||||
/**
|
||||
* 流式查询:逐行回调,不一次性物化全部结果(大表友好)。
|
||||
* 支持简单 SELECT(WHERE/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);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async queryStream<T extends Record<string, unknown> = Record<string, unknown>>(
|
||||
sql: string,
|
||||
onRow: (row: T) => void,
|
||||
): Promise<number> {
|
||||
this.ensureReady();
|
||||
const stmt = parseAll(sql)[0];
|
||||
if (!stmt || stmt.type !== 'SELECT') {
|
||||
throw new DatabaseError('queryStream only supports SELECT statements', 'NOT_SUPPORTED');
|
||||
}
|
||||
const select = stmt as import('./query/ast').SelectStatement;
|
||||
|
||||
// 不可流式场景:JOIN / GROUP BY / HAVING / DISTINCT / 聚合 / UNION / 关联子查询 / ORDER BY
|
||||
const aggregate = select.columns.some((c) => /^(COUNT|SUM|AVG|MIN|MAX)\(/i.test(c));
|
||||
const streamable = !select.joins && !select.groupBy && !select.having && !select.distinct
|
||||
&& !aggregate && !(select.orderBy && select.orderBy.length > 0)
|
||||
&& !(select.where && select.where['$exists'] !== undefined);
|
||||
|
||||
if (streamable && typeof this.engine.findStream === 'function') {
|
||||
// 用户回调为 async(返回 Promise)时引擎同步扫描无法 await → 回退物化
|
||||
const isAsync = (onRow as { constructor?: { name?: string } }).constructor?.name === 'AsyncFunction';
|
||||
if (!isAsync) {
|
||||
const where = this.normalizeWhereForStream(select);
|
||||
const plainCols = select.columns.filter((c) => !/\s+AS\s+\w+$/i.test(c));
|
||||
return this.engine.findStream(select.from, {
|
||||
table: select.from,
|
||||
columns: plainCols.length > 0 && plainCols[0] !== '*' ? plainCols : ['*'],
|
||||
where: where && Object.keys(where).length > 0 ? where : undefined,
|
||||
limit: select.limit,
|
||||
offset: select.offset,
|
||||
}, onRow as (row: Record<string, unknown>) => void);
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:物化后逐行回调
|
||||
const result = await this.query(sql);
|
||||
if (Array.isArray(result)) {
|
||||
for (const row of result as T[]) {
|
||||
await onRow(row);
|
||||
}
|
||||
return result.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** 流式查询用:剥离主表别名前缀(复用 query 路径的规范化逻辑) */
|
||||
private normalizeWhereForStream(select: import('./query/ast').SelectStatement): import('./constants').WhereCondition | undefined {
|
||||
const aliases = [select.alias ?? select.from].filter(Boolean);
|
||||
const strip = (col: string): string => {
|
||||
for (const a of aliases) {
|
||||
if (col.startsWith(`${a}.`)) return col.slice(a.length + 1);
|
||||
}
|
||||
return col;
|
||||
};
|
||||
const walk = (w: import('./constants').WhereCondition): import('./constants').WhereCondition => {
|
||||
const out: import('./constants').WhereCondition = {};
|
||||
for (const [k, v] of Object.entries(w)) {
|
||||
if (k === '$and' || k === '$or') {
|
||||
out[k] = (v as import('./constants').WhereCondition[]).map(walk);
|
||||
} else if (k === '$not' && typeof v === 'object' && v !== null) {
|
||||
out.$not = walk(v as import('./constants').WhereCondition);
|
||||
} else {
|
||||
out[strip(k)] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
return walk(select.where ?? {});
|
||||
}
|
||||
|
||||
// ---- 事务 ----
|
||||
|
||||
/** 执行事务 */
|
||||
|
||||
Reference in New Issue
Block a user