121 lines
3.4 KiB
JavaScript
121 lines
3.4 KiB
JavaScript
// grid-db/src/query/scan.js
|
||
// Grid-DB · 范围扫描器
|
||
// 矩形区域查询
|
||
// PRJ-GDB-001 · Phase 1 · ZY-GDB-P1-003
|
||
// 版权:国作登字-2026-A-00037559
|
||
|
||
'use strict';
|
||
|
||
const GridCell = require('../core/grid-cell');
|
||
|
||
/**
|
||
* RangeScanner — 范围扫描器
|
||
*
|
||
* 本体论锚定:扫描 = 在纸上画一个矩形区域,找出所有落在区域内的格子。
|
||
* 矩形由 xRange 和 yRange 定义,layer 可选过滤。
|
||
*
|
||
* 支持 BTree 索引和 Map 索引两种后端。
|
||
*/
|
||
class RangeScanner {
|
||
/**
|
||
* @param {object} deps
|
||
* @param {Map|object} deps.index 索引(Map 或 BTree,需支持遍历)
|
||
* @param {object} deps.pageManager 页管理器
|
||
*/
|
||
constructor({ index, pageManager }) {
|
||
if (!index) {
|
||
throw new Error('RangeScanner: index 不能为空');
|
||
}
|
||
if (!pageManager) {
|
||
throw new Error('RangeScanner: pageManager 不能为空');
|
||
}
|
||
this._index = index;
|
||
this._pageManager = pageManager;
|
||
}
|
||
|
||
/**
|
||
* 矩形区域扫描
|
||
*
|
||
* @param {string} namespace 命名空间
|
||
* @param {object} [options]
|
||
* @param {number[]} [options.xRange] X 范围 [min, max]
|
||
* @param {number[]} [options.yRange] Y 范围 [min, max]
|
||
* @param {string} [options.layer] 层级过滤
|
||
* @param {number} [options.limit] 最大返回数
|
||
* @param {number} [options.offset] 跳过前 N 条
|
||
* @returns {Array<{cell: GridCell, data: *}>} 按 (gridX, gridY) 排序
|
||
*/
|
||
scan(namespace, options = {}) {
|
||
const { xRange, yRange, layer, limit, offset = 0 } = options;
|
||
const results = [];
|
||
|
||
// 遍历索引中所有条目
|
||
const entries = this._getEntries();
|
||
|
||
for (const [key, pageId] of entries) {
|
||
try {
|
||
const cell = GridCell.fromKey(key);
|
||
|
||
// 命名空间过滤
|
||
if (cell.namespace !== namespace) continue;
|
||
|
||
// 层级过滤
|
||
if (layer && cell.layer !== layer) continue;
|
||
|
||
// X 范围过滤
|
||
if (xRange) {
|
||
if (cell.gridX < xRange[0] || cell.gridX > xRange[1]) continue;
|
||
}
|
||
|
||
// Y 范围过滤
|
||
if (yRange) {
|
||
if (cell.gridY < yRange[0] || cell.gridY > yRange[1]) continue;
|
||
}
|
||
|
||
// 读取数据
|
||
const dataBuf = this._pageManager.readPage(pageId);
|
||
if (dataBuf) {
|
||
let data;
|
||
try { data = JSON.parse(dataBuf.toString('utf-8')); } catch { data = dataBuf; }
|
||
results.push({ cell, data });
|
||
}
|
||
} catch {
|
||
// 跳过无效键格式(fromKey 解析失败),不影响其他键的扫描
|
||
}
|
||
}
|
||
|
||
// 按 (gridX, gridY) 排序
|
||
results.sort((a, b) => {
|
||
if (a.cell.gridX !== b.cell.gridX) return a.cell.gridX - b.cell.gridX;
|
||
return a.cell.gridY - b.cell.gridY;
|
||
});
|
||
|
||
// offset + limit
|
||
const start = offset || 0;
|
||
const end = limit ? start + limit : results.length;
|
||
return results.slice(start, end);
|
||
}
|
||
|
||
/**
|
||
* 获取索引中的所有条目(兼容 Map 和 BTree)
|
||
* @returns {Array<[string, *]>}
|
||
*/
|
||
_getEntries() {
|
||
if (this._index instanceof Map) {
|
||
return Array.from(this._index.entries());
|
||
}
|
||
// BTree:通过 toJSON 获取所有条目
|
||
if (typeof this._index.toJSON === 'function') {
|
||
const json = this._index.toJSON();
|
||
return json.entries.map(e => [e.key, e.value]);
|
||
}
|
||
// 其他可迭代对象
|
||
if (typeof this._index.entries === 'function') {
|
||
return Array.from(this._index.entries());
|
||
}
|
||
return [];
|
||
}
|
||
}
|
||
|
||
module.exports = RangeScanner;
|