@msom/http
Version:
@msom/http
147 lines (140 loc) • 4.92 kB
text/typescript
import { Filter, FilterOperators } from "mongodb";
import { DBContext } from "./DBContext";
import { QueryModel, QueryProtocol, and, comp } from "./QueryProtocolBuilder";
import {
AndCondition,
CompCondition,
CompConditionType,
OrCondition,
QueryCondition,
QueryResultItem,
} from "./interfaces";
import { assert } from "@msom/common";
const FilterTypeMap: Record<CompConditionType, keyof FilterOperators<unknown>> =
{
"!=": "$ne",
"=": "$eq",
"<": "$lt",
"<=": "$lte",
">": "$gt",
">=": "$gte",
in: "$in",
nin: "$nin",
exist: "$exist",
};
export class QueryExecutor {
private cache = new Map<string, Promise<QueryResultItem[]>>();
private declare dbContext: DBContext;
constructor(option: { dbContext: DBContext }) {
Object.assign(option);
}
async clearCache(): Promise<void> {
this.cache.clear();
}
async execute(protocol: QueryProtocol): Promise<QueryResultItem[]> {
const cacheKey = this.generateCacheKey(protocol);
const cache = this.cache.get(cacheKey);
if (cache) {
return cache;
}
const result = this.processProtocol(protocol);
this.cache.set(cacheKey, result);
return result;
}
resolveCondition(condition: QueryCondition | undefined): Filter<unknown> {
if (!condition) {
return {};
}
if (CompCondition.is(condition)) {
return {
[condition.propKey]: {
[FilterTypeMap[condition.compType]]:
condition.compType === "exist" ? true : condition.value,
},
};
}
return {
[AndCondition.is(condition) ? "$and" : "$or"]: (
condition as AndCondition | OrCondition
).conditions.map(this.resolveCondition),
};
}
private async processProtocol(
protocol: QueryProtocol
): Promise<QueryResultItem[]> {
// TODO:
const process = async (option: QueryModel) => {
const { modelName, conditions: condition } = option;
const collection = this.dbContext.getCollection(modelName);
const models: QueryResultItem[] = (
await collection.find(this.resolveCondition(condition)).toArray()
).map((_model) => {
const { _id, ...model } = _model;
return {
_id,
model,
relates: {},
};
});
/**
* TODO
* 判断查询条件有没有关系,即relates是否为空对象
* 否,则直接返回models
* 有关系查询,则找到对应模型的meta元数据,处理meta.relations中存在的搞关系
* 在meta.relating中找到对应关系,根据实例id去找到关联的实例id
* 并生成比对id的condition和查询条件中对应的RelateModel的condition合并
* 生成新的QueryModel,包含对应RelateModel的relates和meta中对应关系的modelName和合并后的condition
* 递归处理新的QueryModel,将返回结果添加在当前model实例的relates中
*/
const relateKeys = Reflect.ownKeys(option.relates) as string[];
if (relateKeys.length === 0) {
return models;
}
const relateHandles = models.map(async ({ _id, relates }) => {
const handles = relateKeys
.map((relationName) => {
const meta = this.dbContext.getModelMeta(modelName);
assert(meta, "unknown modelName: " + modelName);
if (
!Reflect.has(meta.relations, relationName) ||
!Reflect.has(meta.relating, relationName)
) {
return;
}
const relationMeta = Reflect.get(
meta.relations,
relationName,
meta.relations
);
if (!relationMeta) {
return;
}
const relating = meta.relating[relationName].get(_id);
if (!relating) {
return;
}
const relateModel = option.relates[relationName];
const { correspondingModel } = relationMeta;
const newOption = new QueryModel(correspondingModel);
newOption.relates = relateModel.relates;
newOption.conditions = and(
...([relateModel.conditions, comp("in", "_id", relating)].filter(
Boolean
) as QueryCondition[])
);
return process(newOption).then((relations) => {
relates[relationName] = relations;
});
})
.filter(Boolean) as Promise<unknown>[];
return Promise.all(handles);
});
await Promise.all(relateHandles);
return models;
};
return await process(protocol.option);
}
private generateCacheKey(protocol: QueryProtocol): string {
return JSON.stringify(protocol);
}
}