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:
+40
-12
@@ -209,18 +209,31 @@ export class QueryExecutor {
|
||||
|
||||
// v0.7.0: 真实索引命中信息(此前 usingIndex 恒为 'auto' 占位)。
|
||||
// 引擎无关启发式:WHERE 中存在主键/索引/唯一列条件 → 对应引擎索引路径。
|
||||
// v0.7.3: 递归识别 $and 嵌套等值条件(与 Memory/Aria 的 $and 下推行为对齐;
|
||||
// $or/$not 不下推,保持 none)。
|
||||
let usingIndex: string = plan?.table ? 'none' : 'none';
|
||||
if (plan && plan.table && plan.where && Object.keys(plan.where).length > 0) {
|
||||
try {
|
||||
const schema = await this.engine.getTableSchema(plan.table);
|
||||
if (schema) {
|
||||
for (const col of Object.keys(plan.where)) {
|
||||
if (col.startsWith('$')) continue;
|
||||
const colDef = schema.columns[col];
|
||||
if (!colDef) continue;
|
||||
if (colDef.primaryKey) { usingIndex = 'pk'; break; }
|
||||
if (colDef.index || colDef.unique) { usingIndex = `index:${col}`; break; }
|
||||
}
|
||||
const findIndex = (w: import('../constants').WhereCondition): string | null => {
|
||||
for (const [k, v] of Object.entries(w)) {
|
||||
if (k === '$and') {
|
||||
for (const sub of (v as import('../constants').WhereCondition[])) {
|
||||
const hit = findIndex(sub);
|
||||
if (hit) return hit;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (k === '$or' || k === '$not') continue;
|
||||
const colDef = schema.columns[k];
|
||||
if (!colDef) continue;
|
||||
if (colDef.primaryKey) return 'pk';
|
||||
if (colDef.index || colDef.unique) return `index:${k}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
usingIndex = findIndex(plan.where) ?? 'none';
|
||||
}
|
||||
} catch { /* schema 读取失败保持 none */ }
|
||||
}
|
||||
@@ -341,7 +354,11 @@ export class QueryExecutor {
|
||||
rows = rows.filter((row) => matchWhere(row, stmt.having!));
|
||||
}
|
||||
if (stmt.orderBy && stmt.orderBy.length > 0) rows = applyOrderBy(rows, stmt.orderBy);
|
||||
if (!hasGroupBy && !hasAggregate && stmt.columns.length > 0 && stmt.columns[0] !== '*') {
|
||||
// v0.7.3: `SELECT *, col AS alias` —— 此前 columns[0]==='*' 直接不投影,
|
||||
// 别名列/常量列丢失。仅当 '*' 是唯一列时跳过投影(projectRow 对裸 '*'
|
||||
// 合并原行全部列,其余表达式覆盖/追加)
|
||||
if (!hasGroupBy && !hasAggregate && stmt.columns.length > 0
|
||||
&& !(stmt.columns.length === 1 && stmt.columns[0] === '*')) {
|
||||
rows = rows.map((row) => this.projectRow(row, stmt.columns));
|
||||
}
|
||||
// v0.3.3: ORDER BY 别名 → 投影后才存在,需在投影后重新排序
|
||||
@@ -942,8 +959,13 @@ export class QueryExecutor {
|
||||
const aliasCols: { alias: string; source: string }[] = [];
|
||||
const caseCols: { alias: string; expr: CaseExpression }[] = [];
|
||||
const constCols: { key: string; value: unknown }[] = [];
|
||||
// v0.7.3: 裸 '*' 与列表达式混合(SELECT *, name AS nick)→ 原行全部列为基
|
||||
let hasStar = false;
|
||||
for (const col of columns) {
|
||||
if (col === '*') continue;
|
||||
if (col === '*') {
|
||||
hasStar = true;
|
||||
continue;
|
||||
}
|
||||
const expr = parseCaseExpression(col);
|
||||
if (expr) {
|
||||
caseCols.push({ alias: expr.alias ?? col, expr });
|
||||
@@ -957,19 +979,25 @@ export class QueryExecutor {
|
||||
// v0.4.0: 字符串常量列 SELECT 'lit' → 常量输出
|
||||
const lit = col.match(/^'(.*)'$/s);
|
||||
if (lit) {
|
||||
const value = lit[1].replace(/\\'/g, "'");
|
||||
// v0.7.3: SQL 标准 '' 转义还原(readString 已把 '' 合并为单个 ',
|
||||
// 打包回列的文本中相邻两个 ' 即一个引号字面量)
|
||||
const value = lit[1].replace(/''/g, "'");
|
||||
constCols.push({ key: col, value });
|
||||
continue;
|
||||
}
|
||||
plain.push(col);
|
||||
}
|
||||
const projected = plain.length > 0 ? projectColumns(row, plain) : {};
|
||||
// v0.7.3: hasStar 时以原行全部列为基(projectColumns 仅投影 plain 列,不含 * 的其余列)
|
||||
const projected = hasStar
|
||||
? { ...row }
|
||||
: (plain.length > 0 ? projectColumns(row, plain) : {});
|
||||
for (const { alias, source } of aliasCols) {
|
||||
if (source === '*') {
|
||||
Object.assign(projected, row);
|
||||
} else {
|
||||
const lit = source.match(/^'(.*)'$/s);
|
||||
projected[alias] = lit ? lit[1].replace(/\\'/g, "'") : row[source];
|
||||
// v0.7.3: 同 constCols —— SQL 标准 '' 转义还原
|
||||
projected[alias] = lit ? lit[1].replace(/''/g, "'") : row[source];
|
||||
}
|
||||
}
|
||||
for (const { key, value } of constCols) {
|
||||
|
||||
Reference in New Issue
Block a user