Skip to content

Commit b5fa4e5

Browse files
committed
add distinct
1 parent 11fbbe9 commit b5fa4e5

5 files changed

Lines changed: 509 additions & 6 deletions

File tree

STATUS.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,10 @@
9898
- 自动记录慢查询日志;触发 slow-query 和 query 事件;不支持缓存(流式特性)。
9999
- ✅ 聚合(aggregate)
100100
- 支持 MongoDB 聚合管道透传;支持 maxTimeMS/allowDiskUse/hint/collation/comment;默认禁用缓存(cache=0);可选返回 meta 耗时信息。
101-
- distinct
102-
- 仅 Mongo 适配器语义;尚未纳入抽象
101+
- distinct
102+
- 支持字段去重查询;支持 query/maxTimeMS/collation/hint;默认启用缓存;可选返回 meta 耗时信息
103103
- ❌ explain
104104
- 诊断用途;建议透传且禁用缓存(规划中)。
105-
- ❌ countDocuments / estimatedDocumentCount
106-
- 目前仅对外提供 count 抽象;计划拆分补充。
107105
- ❌ 高级查询/游标选项统一抽象
108106
- batchSize/hint/collation/noCursorTimeout/tailable/max/min/returnKey/allowPartialResults/
109107
readPreference/readConcern。

examples/distinct-demo.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* distinct 方法示例
3+
* 演示字段去重查询的各种用法
4+
*/
5+
6+
const MonSQLize = require('../lib/index');
7+
8+
async function main() {
9+
// 初始化 MonSQLize
10+
const db = new MonSQLize({
11+
type: 'mongodb',
12+
databaseName: 'test',
13+
config: {
14+
uri: process.env.MONGO_URI || 'mongodb://localhost:27017'
15+
},
16+
cache: { maxSize: 1000 },
17+
logger: console,
18+
maxTimeMS: 5000,
19+
slowQueryMs: 100
20+
});
21+
22+
try {
23+
await db.connect();
24+
const collection = db.collection('users');
25+
26+
console.log('\n=== distinct 方法示例 ===\n');
27+
28+
// 示例 1:基础用法 - 获取所有不同的状态值
29+
console.log('1. 基础用法:获取所有不同的状态值');
30+
const statuses = await collection.distinct('status');
31+
console.log('所有状态:', statuses);
32+
console.log('状态数量:', statuses.length);
33+
34+
// 示例 2:带查询条件 - 获取年龄大于 18 的用户的所有不同城市
35+
console.log('\n2. 带查询条件:年龄大于 18 的用户城市');
36+
const cities = await collection.distinct('city', {
37+
query: { age: { $gt: 18 } }
38+
});
39+
console.log('城市列表:', cities);
40+
41+
// 示例 3:嵌套字段 - 获取所有不同的用户角色
42+
console.log('\n3. 嵌套字段:获取用户角色');
43+
const roles = await collection.distinct('profile.role', {
44+
query: { 'profile.active': true }
45+
});
46+
console.log('角色列表:', roles);
47+
48+
// 示例 4:带缓存 - 缓存 60 秒
49+
console.log('\n4. 带缓存:缓存 60 秒');
50+
const t1 = Date.now();
51+
const tags1 = await collection.distinct('tags', {
52+
query: { published: true },
53+
cache: 60000
54+
});
55+
console.log('首次查询耗时:', Date.now() - t1, 'ms');
56+
console.log('标签数量:', tags1.length);
57+
58+
// 再次查询,应该命中缓存
59+
const t2 = Date.now();
60+
const tags2 = await collection.distinct('tags', {
61+
query: { published: true },
62+
cache: 60000
63+
});
64+
console.log('缓存查询耗时:', Date.now() - t2, 'ms');
65+
console.log('结果一致:', JSON.stringify(tags1) === JSON.stringify(tags2));
66+
67+
// 示例 5:带 meta 信息 - 查看查询耗时
68+
console.log('\n5. 带 meta 信息:查看查询详情');
69+
const result = await collection.distinct('department', {
70+
query: { active: true },
71+
cache: 30000,
72+
maxTimeMS: 3000,
73+
meta: true
74+
});
75+
console.log('部门列表:', result.data);
76+
console.log('查询元信息:');
77+
console.log(' - 操作:', result.meta.op);
78+
console.log(' - 耗时:', result.meta.durationMs, 'ms');
79+
console.log(' - 命名空间:', result.meta.ns);
80+
console.log(' - 是否命中缓存:', result.meta.fromCache || false);
81+
82+
// 示例 6:带 hint 索引提示
83+
console.log('\n6. 带索引提示(hint)');
84+
const countries = await collection.distinct('country', {
85+
query: { status: 'active' },
86+
hint: { status: 1, country: 1 }
87+
});
88+
console.log('国家列表:', countries);
89+
90+
// 示例 7:带 collation 排序规则(大小写不敏感)
91+
console.log('\n7. 带排序规则(collation)');
92+
const names = await collection.distinct('name', {
93+
query: {},
94+
collation: { locale: 'en', strength: 2 } // 大小写不敏感
95+
});
96+
console.log('名字列表:', names);
97+
98+
// 示例 8:数组字段去重
99+
console.log('\n8. 数组字段去重');
100+
const allTags = await collection.distinct('tags', {
101+
query: {}
102+
});
103+
console.log('所有标签(数组展开后):', allTags);
104+
105+
// 示例 9:缓存失效
106+
console.log('\n9. 缓存失效演示');
107+
108+
// 先查询并缓存
109+
await collection.distinct('status', { cache: 60000 });
110+
console.log('已缓存 status 查询');
111+
112+
// 失效特定操作的缓存
113+
const deleted = await collection.invalidate('distinct');
114+
console.log('失效 distinct 缓存,删除键数:', deleted);
115+
116+
// 再次查询,不会命中缓存
117+
const { meta } = await collection.distinct('status', {
118+
cache: 60000,
119+
meta: true
120+
});
121+
console.log('缓存已失效,fromCache:', meta.fromCache || false);
122+
123+
// 示例 10:慢查询日志监听
124+
console.log('\n10. 慢查询事件监听');
125+
126+
// 注册慢查询监听器
127+
db.on('slow-query', (meta) => {
128+
console.log('🐌 检测到慢查询:', {
129+
op: meta.op,
130+
durationMs: meta.durationMs,
131+
collection: meta.ns.coll
132+
});
133+
});
134+
135+
// 执行一个可能较慢的查询(设置较低的慢查询阈值)
136+
await collection.distinct('email', {
137+
query: { age: { $gte: 18, $lte: 65 } },
138+
maxTimeMS: 5000
139+
});
140+
141+
// 示例 11:综合示例 - 统计分析
142+
console.log('\n11. 综合示例:用户统计分析');
143+
144+
const [
145+
uniqueStatuses,
146+
uniqueCities,
147+
uniqueRoles,
148+
uniqueDepartments
149+
] = await Promise.all([
150+
collection.distinct('status'),
151+
collection.distinct('city', { query: { country: 'China' } }),
152+
collection.distinct('role'),
153+
collection.distinct('department', { query: { active: true } })
154+
]);
155+
156+
console.log('统计摘要:');
157+
console.log(' - 状态类型数:', uniqueStatuses.length);
158+
console.log(' - 中国城市数:', uniqueCities.length);
159+
console.log(' - 角色类型数:', uniqueRoles.length);
160+
console.log(' - 活跃部门数:', uniqueDepartments.length);
161+
162+
console.log('\n=== 演示完成 ===\n');
163+
164+
} catch (error) {
165+
console.error('错误:', error.message);
166+
console.error('堆栈:', error.stack);
167+
} finally {
168+
await db.close();
169+
console.log('数据库连接已关闭');
170+
}
171+
}
172+
173+
// 运行示例
174+
if (require.main === module) {
175+
main().catch(console.error);
176+
}
177+
178+
module.exports = { main };
179+

index.d.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ declare module 'monsqlize' {
110110
meta?: boolean | MetaOptions; // 返回耗时元信息
111111
}
112112

113+
interface DistinctOptions {
114+
query?: any; // 过滤条件,只对匹配的文档进行去重
115+
cache?: number; // 缓存时间(毫秒),默认继承实例缓存配置
116+
maxTimeMS?: number; // 查询超时时间(毫秒)
117+
collation?: any; // 排序规则(可选)
118+
hint?: string | object; // 索引提示(可选)
119+
meta?: boolean | MetaOptions; // 返回耗时元信息
120+
}
121+
113122
interface StreamOptions {
114123
query?: any; // 查询条件
115124
projection?: Record<string, any> | string[]; // 字段投影
@@ -255,13 +264,18 @@ declare module 'monsqlize' {
255264
aggregate(pipeline: any[], options: AggregateOptions & { meta: true | MetaOptions }): Promise<ResultWithMeta<any[]>>;
256265
aggregate(pipeline?: any[], options?: AggregateOptions): Promise<any[] | ResultWithMeta<any[]>>;
257266

267+
// distinct 重载:支持 meta 参数
268+
distinct<T = any>(field: string, options?: Omit<DistinctOptions, 'meta'>): Promise<T[]>;
269+
distinct<T = any>(field: string, options: DistinctOptions & { meta: true | MetaOptions }): Promise<ResultWithMeta<T[]>>;
270+
distinct<T = any>(field: string, options?: DistinctOptions): Promise<T[] | ResultWithMeta<T[]>>;
271+
258272
// stream:返回 Node.js 可读流
259273
stream(options?: StreamOptions): NodeJS.ReadableStream;
260274

261275
// findPage:已在 PageResult 中包含 meta 字段,无需重载
262276
findPage(options: FindPageOptions): Promise<PageResult>;
263277

264-
invalidate(op?: 'find' | 'findOne' | 'count' | 'findPage' | 'aggregate'): Promise<number>;
278+
invalidate(op?: 'find' | 'findOne' | 'count' | 'findPage' | 'aggregate' | 'distinct'): Promise<number>;
265279
}
266280

267281
type DbAccessor = {

lib/mongodb/index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ module.exports = class {
353353
* 聚合查询(MongoDB 聚合管道透传)
354354
* @param {Array} pipeline - 聚合管道数组,如 [{ $match: {...} }, { $group: {...} }]
355355
* @param {Object} [options={}] - 聚合选项
356-
* @param {number} [options.cache=0] - 缓存时间(毫秒),默认不缓存(聚合通常动态性强)
356+
* @param {number} [options.cache=0] - 缓存时间(毫秒),��认不缓存(聚合通常动态性强)
357357
* @param {number} [options.maxTimeMS] - 查询超时时间(毫秒)
358358
* @param {boolean} [options.allowDiskUse=false] - 是否允许使用磁盘(默认 false)
359359
* @param {Object} [options.collation] - 排序规则(可选)
@@ -427,6 +427,39 @@ module.exports = class {
427427
);
428428
},
429429

430+
/**
431+
* 字段去重查询
432+
* @description 对指定字段进行去重查询,返回该字段的所有唯一值数组
433+
* @param {string} field - 要去重的字段名,支持嵌套字段(如 'user.name')
434+
* @param {Object} [options={}] - 查询选项配置对象
435+
* @param {Object} [options.query={}] - 过滤条件,只对匹配的文档进行去重
436+
* @param {number} [options.cache] - 缓存时间(毫秒),默认继承实例缓存配置
437+
* @param {number} [options.maxTimeMS] - 查询超时时间(毫秒)
438+
* @param {Object} [options.collation] - 排序规则(可选)
439+
* @param {string|Object} [options.hint] - 索引提示(可选)
440+
* @param {boolean|Object} [options.meta] - 是否返回耗时元信息
441+
* @returns {Promise<Array>} 返回去重后的值数组
442+
* @example
443+
*/
444+
distinct: async (field, options = {}) => {
445+
const {
446+
query = {},
447+
maxTimeMS = this.defaults.maxTimeMS,
448+
collation,
449+
hint
450+
} = options;
451+
452+
const driverOpts = { maxTimeMS };
453+
if (collation) driverOpts.collation = collation;
454+
if (hint) driverOpts.hint = hint;
455+
456+
return run(
457+
'distinct',
458+
options,
459+
() => collection.distinct(field, query, driverOpts)
460+
);
461+
},
462+
430463
/**
431464
* 深度分页(统一版:游标 after/before + 跳页 page + 可选 offset/totals)
432465
* @param {Object} [options={}] - 兼容原参数,并扩展 page/jump/offsetJump/totals

0 commit comments

Comments
 (0)