feat: v0.7.0 参数化查询 + 事务增量 flush + 复合主键语义硬化 + EXPLAIN 真实索引信息
- 参数化查询 db.query(sql, params):词法层 ? 绑定 + SQL 字面量安全编码 ('' 转义/注入防护);参数计数不匹配 PARAM_ERROR;对象参数显式拒绝 - KVStoreEngine 事务增量 flush:行级变更追踪,commit 仅写改动行 (1000 行表改 1 行:日志 1 条目 vs 整表 1000 条目);级联影响表漏写修复 (txFullTables 同步加入 txDirtyTables);移除每次 commit 全量 checkpoint (阈值自动 checkpoint + close 统一截断) - 复合主键显式拒绝:createSchema 校验期 SCHEMA_ERROR + ALTER ADD 主键列防护 (此前静默取第一个主键,其余标记失效) - EXPLAIN usingIndex 真实命中信息:pk / index:col / none 测试 1126 → 1147(72 套件);行覆盖率 89.8%;版本 0.7.0
This commit is contained in:
+31
-1
@@ -207,6 +207,24 @@ export class QueryExecutor {
|
||||
plan = compileStatement(stmt.query);
|
||||
} catch { /* 非查询语句无 QueryPlan */ }
|
||||
|
||||
// v0.7.0: 真实索引命中信息(此前 usingIndex 恒为 'auto' 占位)。
|
||||
// 引擎无关启发式:WHERE 中存在主键/索引/唯一列条件 → 对应引擎索引路径。
|
||||
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; }
|
||||
}
|
||||
}
|
||||
} catch { /* schema 读取失败保持 none */ }
|
||||
}
|
||||
|
||||
return {
|
||||
type: stmt.query.type,
|
||||
table: plan?.table,
|
||||
@@ -215,7 +233,7 @@ export class QueryExecutor {
|
||||
orderBy: plan?.orderBy || [],
|
||||
limit: plan?.limit,
|
||||
offset: plan?.offset,
|
||||
usingIndex: plan?.table ? 'auto' : 'none',
|
||||
usingIndex,
|
||||
estimatedRows: rows,
|
||||
actualTimeMs: elapsed,
|
||||
};
|
||||
@@ -713,6 +731,18 @@ export class QueryExecutor {
|
||||
const schema = await this.engine.getTableSchema(stmt.name);
|
||||
if (!schema) return;
|
||||
|
||||
// v0.7.0: ALTER ADD 主键列防护 —— 复合主键不支持(与 createSchema 校验对齐),
|
||||
// 避免绕过建表校验添加第二个主键列导致语义陷阱
|
||||
if (stmt.action === 'ADD' && stmt.column.primaryKey) {
|
||||
const hasPk = Object.values(schema.columns).some((c) => c.primaryKey);
|
||||
if (hasPk) {
|
||||
throw new DatabaseError(
|
||||
`Composite primary keys are not supported yet: table "${stmt.name}" already has a primary key column`,
|
||||
'SCHEMA_ERROR',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// v0.4.1: 引擎级 alterTable(Aria 需重写存储行 + 持久化 schema;其余引擎走通用引用路径)
|
||||
if (typeof this.engine.alterTable === 'function') {
|
||||
return this.engine.alterTable(stmt.name, stmt.action, { ...astColumnToColumnDef(stmt.column), name: stmt.column.name });
|
||||
|
||||
Reference in New Issue
Block a user