fix: v0.7.3 数据正确性与边界窗口收尾 — INSERT 语句级原子(三引擎+Aria PK 批内重复)/ 索引列 IS NULL 恒空 / delete RESTRICT 破坏索引 / queryStream 子查询静默空结果 / ALTER DROP 索引残留 / UNIQUE INDEX 存量校验 / SELECT * 别名投影 / WAL BEGIN/ROLLBACK 事务边界 / aria $in 与级联重复扫描性能 / $and 等值下推 / ANALYZE 索引统计 / React-Vue hooks 生命周期 / 迁移主键兜底 + 58 回归
CI / test (18.x) (push) Successful in 17m43s
CI / test (22.x) (push) Successful in 13m45s
CI / test (20.x) (push) Successful in 15m33s
CI / test (24.x) (push) Successful in 24m46s
CI / e2e (push) Successful in 52s

This commit is contained in:
thzxx
2026-08-14 22:53:46 +08:00
parent f8f8d1b2ff
commit 50468b9b0e
29 changed files with 2374 additions and 650 deletions
+39 -2
View File
@@ -270,9 +270,36 @@ export class MetonaSqlark {
// 不可流式场景:JOIN / GROUP BY / HAVING / DISTINCT / 聚合 / UNION / 关联子查询 / ORDER BY
const aggregate = select.columns.some((c) => /^(COUNT|SUM|AVG|MIN|MAX)\(/i.test(c));
// v0.7.3: WHERE 含子查询($subquery / $exists / 嵌套 $col 列引用)不可流式 ——
// 引擎层 matchWhere 的 $in/$nin 遇未解析的 $subquery 对象返回 false → 所有行
// 被静默过滤(空结果);$col 操作符无对应匹配分支会抛 QUERY_ERROR。
// 递归检测后回退物化路径(resolveSubqueries 正确解析)。
const hasSubquery = (where: import('./constants').WhereCondition | undefined): boolean => {
if (!where) return false;
for (const [k, v] of Object.entries(where)) {
if (k === '$and' || k === '$or') {
if ((v as import('./constants').WhereCondition[]).some((sub) => hasSubquery(sub))) return true;
continue;
}
if (k === '$not') {
if (hasSubquery(v as import('./constants').WhereCondition)) return true;
continue;
}
if (k === '$exists') return true;
if (typeof v === 'object' && v !== null) {
for (const [, operand] of Object.entries(v as Record<string, unknown>)) {
if (typeof operand === 'object' && operand !== null) {
const ops = operand as Record<string, unknown>;
if ('$subquery' in ops || '$col' in ops) return true;
}
}
}
}
return false;
};
const streamable = !select.joins && !select.groupBy && !select.having && !select.distinct
&& !aggregate && !(select.orderBy && select.orderBy.length > 0)
&& !(select.where && select.where['$exists'] !== undefined);
&& !hasSubquery(select.where);
if (streamable && typeof this.engine.findStream === 'function') {
// 用户回调为 async(返回 Promise)时引擎同步扫描无法 await → 回退物化
@@ -441,9 +468,19 @@ export class MetonaSqlark {
private async triggerStatementHooks(stmt: Statement, phase: 'before' | 'after', result?: unknown): Promise<void> {
switch (stmt.type) {
case 'INSERT': {
// v0.7.3: 列映射对齐 executor —— 省略列名时按 schema 列顺序映射
// (此前用数字键 String(i),与 executor 写入的真实行键不一致)
let cols: string[] = stmt.columns ?? [];
if (cols.length === 0) {
try {
const schema = await this.engine.getTableSchema(stmt.into);
cols = schema ? Object.keys(schema.columns) : [];
} catch {
cols = [];
}
}
const rows: Record<string, unknown>[] = (stmt.values ?? []).map((vals: unknown[]) => {
const row: Record<string, unknown> = {};
const cols = stmt.columns ?? [];
for (let i = 0; i < vals.length; i++) {
row[cols[i] ?? String(i)] = vals[i];
}