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 回归
This commit is contained in:
+39
-2
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user