aggregation.test.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. import * as assert from 'power-assert'
  2. import tcb from '../../../src/index'
  3. import * as Config from '../../config.local'
  4. import * as common from '../../common/index'
  5. const app = tcb.init(Config)
  6. const db = app.database()
  7. const collName = 'db-test-aggregate'
  8. const collection = db.collection(collName)
  9. describe('sample', () => {
  10. it('sample', async () => {
  11. const data = [{ name: 'a' }, { name: 'b' }]
  12. const usersCollection = await common.safeCollection(db, 'test-users')
  13. const createSuccess = await usersCollection.create(data)
  14. assert.strictEqual(createSuccess, true)
  15. const result = await db
  16. .collection('test-users')
  17. .aggregate()
  18. .sample({
  19. size: 1
  20. })
  21. .end()
  22. assert.strictEqual(result.data.length, 1)
  23. usersCollection.remove()
  24. })
  25. })
  26. describe('sortByCount', () => {
  27. let passagesCollection = null
  28. const data = [
  29. { category: 'Web', tags: ['JavaScript', 'C#'] },
  30. { category: 'Web', tags: ['Go', 'C#'] },
  31. { category: 'Life', tags: ['Go', 'Python', 'JavaScript'] }
  32. ]
  33. beforeAll(async () => {
  34. passagesCollection = await common.safeCollection(db, 'test-sortByCount')
  35. const success = await passagesCollection.create(data)
  36. assert.strictEqual(success, true)
  37. })
  38. afterAll(async () => {
  39. const success = await passagesCollection.remove()
  40. assert.strictEqual(success, true)
  41. })
  42. it('统计基础类型', async () => {
  43. const result = await db
  44. .collection('test-sortByCount')
  45. .aggregate()
  46. .sortByCount('$category')
  47. .end()
  48. assert.strictEqual(result.data.length, 2)
  49. })
  50. it('解构数组类型', async () => {
  51. const result = await db
  52. .collection('test-sortByCount')
  53. .aggregate()
  54. .unwind('$tags')
  55. .sortByCount('$tags')
  56. .end()
  57. assert.strictEqual(result.data.length, 4)
  58. })
  59. })
  60. describe('match', () => {
  61. let coll = null
  62. const $ = db.command.aggregate
  63. const _ = db.command
  64. const data = [
  65. { author: 'stark', score: 80 },
  66. { author: 'stark', score: 85 },
  67. { author: 'bob', score: 60 },
  68. { author: 'li', score: 55 },
  69. { author: 'jimmy', score: 60 },
  70. { author: 'li', score: 94 },
  71. { author: 'justan', score: 95 }
  72. ]
  73. beforeAll(async () => {
  74. coll = await common.safeCollection(db, 'articles')
  75. const success = await coll.create(data)
  76. assert.strictEqual(success, true)
  77. })
  78. afterAll(async () => {
  79. const success = await coll.remove()
  80. assert.strictEqual(success, true)
  81. })
  82. it('匹配 字段 or 逻辑', async () => {
  83. const result = await db
  84. .collection('articles')
  85. .aggregate()
  86. .match({
  87. author: _.or(_.eq('stark'), _.neq('stark'))
  88. })
  89. .end()
  90. assert(result.data.length === data.length)
  91. })
  92. it('匹配', async () => {
  93. const result = await db
  94. .collection('articles')
  95. .aggregate()
  96. .match({
  97. author: 'stark'
  98. })
  99. .end()
  100. assert.strictEqual(result.data[0].author, 'stark')
  101. })
  102. it('计数', async () => {
  103. const { sum } = db.command.aggregate
  104. const { gt } = db.command
  105. const result = await db
  106. .collection('articles')
  107. .aggregate()
  108. .match({
  109. score: gt(80)
  110. })
  111. .group({
  112. _id: null,
  113. count: sum(1)
  114. })
  115. .end()
  116. assert.strictEqual(result.data[0].count, 3)
  117. })
  118. })
  119. describe('project', () => {
  120. let coll = null
  121. const data = [
  122. {
  123. title: 'This is title',
  124. author: 'Nobody',
  125. isbn: '123456789',
  126. introduction: '......'
  127. }
  128. ]
  129. beforeAll(async () => {
  130. coll = await common.safeCollection(db, 'articles')
  131. const success = await coll.create(data)
  132. assert.strictEqual(success, true)
  133. })
  134. afterAll(async () => {
  135. const success = await coll.remove()
  136. assert.strictEqual(success, true)
  137. })
  138. it('指定包含某些字段', async () => {
  139. const result = await db
  140. .collection('articles')
  141. .aggregate()
  142. .project({
  143. title: 1,
  144. author: 1
  145. })
  146. .end()
  147. assert(result.data[0].author)
  148. assert(result.data[0].title)
  149. assert(!result.data[0].isbn)
  150. })
  151. it('去除输出中的 _id 字段', async () => {
  152. const result = await db
  153. .collection('articles')
  154. .aggregate()
  155. .project({
  156. _id: 0,
  157. title: 1,
  158. author: 1
  159. })
  160. .end()
  161. assert.deepStrictEqual(result.data[0], {
  162. author: 'Nobody',
  163. title: 'This is title'
  164. })
  165. })
  166. it('加入计算出的新字段', async () => {
  167. const data = [
  168. {
  169. name: '小明',
  170. scores: {
  171. chinese: 80,
  172. math: 90,
  173. english: 70
  174. }
  175. }
  176. ]
  177. const usersCollection = await common.safeCollection(db, 'test-users')
  178. const createSuccess = await usersCollection.create(data)
  179. assert.strictEqual(createSuccess, true)
  180. const { sum } = db.command.aggregate
  181. const result = await db
  182. .collection('test-users')
  183. .aggregate()
  184. .project({
  185. _id: 0,
  186. name: 1,
  187. totalScore: sum(['$scores.chinese', '$scores.math', '$scores.english'])
  188. })
  189. .end()
  190. assert.deepStrictEqual(result.data[0], {
  191. name: '小明',
  192. totalScore: 240
  193. })
  194. await usersCollection.remove()
  195. })
  196. it('加入新的数组字段', async () => {
  197. const data = [
  198. { x: 1, y: 1 },
  199. { x: 2, y: 2 },
  200. { x: 3, y: 3 }
  201. ]
  202. const usersCollection = await common.safeCollection(db, 'test-users')
  203. const createSuccess = await usersCollection.create(data)
  204. assert.strictEqual(createSuccess, true)
  205. const result = await db
  206. .collection('test-users')
  207. .aggregate()
  208. .project({
  209. _id: 0,
  210. coordinate: ['$x', '$y']
  211. })
  212. .end()
  213. assert.deepStrictEqual(result.data[0], {
  214. coordinate: [1, 1]
  215. })
  216. await usersCollection.remove()
  217. })
  218. })
  219. describe('replaceRoot', () => {
  220. it('使用已有字段作为根节点', async () => {
  221. const data = [
  222. {
  223. name: 'SFLS',
  224. teachers: {
  225. chinese: 22,
  226. math: 18,
  227. english: 21,
  228. other: 123
  229. }
  230. }
  231. ]
  232. const usersCollection = await common.safeCollection(db, 'test-users')
  233. const createSuccess = await usersCollection.create(data)
  234. assert.strictEqual(createSuccess, true)
  235. const result = await db
  236. .collection('test-users')
  237. .aggregate()
  238. .replaceRoot({
  239. newRoot: '$teachers'
  240. })
  241. .end()
  242. assert.deepStrictEqual(result.data[0], {
  243. chinese: 22,
  244. math: 18,
  245. english: 21,
  246. other: 123
  247. })
  248. await usersCollection.remove()
  249. })
  250. it('使用计算出的新字段作为根节点', async () => {
  251. const data = [
  252. { first_name: '四郎', last_name: '黄' },
  253. { first_name: '邦德', last_name: '马' },
  254. { first_name: '牧之', last_name: '张' }
  255. ]
  256. const usersCollection = await common.safeCollection(db, 'test-users')
  257. const createSuccess = await usersCollection.create(data)
  258. assert.strictEqual(createSuccess, true)
  259. const { concat } = db.command.aggregate
  260. const result = await db
  261. .collection('test-users')
  262. .aggregate()
  263. .replaceRoot({
  264. newRoot: {
  265. full_name: concat(['$last_name', '$first_name'])
  266. }
  267. })
  268. .end()
  269. assert.deepStrictEqual(result.data[0], {
  270. full_name: '黄四郎'
  271. })
  272. await usersCollection.remove()
  273. })
  274. })
  275. describe('skip', () => {
  276. let coll = null
  277. const data = [
  278. { author: 'stark', score: 80 },
  279. { author: 'stark', score: 85 },
  280. { author: 'bob', score: 60 },
  281. { author: 'li', score: 55 },
  282. { author: 'jimmy', score: 60 },
  283. { author: 'li', score: 94 },
  284. { author: 'justan', score: 95 }
  285. ]
  286. beforeAll(async () => {
  287. coll = await common.safeCollection(db, 'articles')
  288. const success = await coll.create(data)
  289. assert.strictEqual(success, true)
  290. })
  291. afterAll(async () => {
  292. const success = await coll.remove()
  293. assert.strictEqual(success, true)
  294. })
  295. it('跳过一定数量的文档', async () => {
  296. const result = await db
  297. .collection('articles')
  298. .aggregate()
  299. .skip(6)
  300. .project({
  301. _id: 0
  302. })
  303. .end()
  304. assert.deepStrictEqual(result.data[0], { author: 'justan', score: 95 })
  305. })
  306. })
  307. describe('sort', () => {
  308. let coll = null
  309. const data = [
  310. { author: 'stark', score: 80, age: 18 },
  311. { author: 'bob', score: 60, age: 18 },
  312. { author: 'li', score: 55, age: 19 },
  313. { author: 'jimmy', score: 60, age: 22 },
  314. { author: 'justan', score: 95, age: 33 }
  315. ]
  316. beforeAll(async () => {
  317. coll = await common.safeCollection(db, 'articles')
  318. const success = await coll.create(data)
  319. assert.strictEqual(success, true)
  320. })
  321. afterAll(async () => {
  322. const success = await coll.remove()
  323. assert.strictEqual(success, true)
  324. })
  325. it('根据已有字段排序', async () => {
  326. const result = await db
  327. .collection('articles')
  328. .aggregate()
  329. .sort({
  330. age: -1,
  331. score: -1
  332. })
  333. .project({
  334. _id: 0
  335. })
  336. .end()
  337. assert.deepStrictEqual(result.data[0], {
  338. author: 'justan',
  339. score: 95,
  340. age: 33
  341. })
  342. assert.deepStrictEqual(result.data[result.data.length - 1], {
  343. author: 'bob',
  344. score: 60,
  345. age: 18
  346. })
  347. })
  348. })
  349. describe('unwind', () => {
  350. let coll = null
  351. const data = [
  352. { product: 'tshirt', size: ['S', 'M', 'L'] },
  353. { product: 'pants', size: [] },
  354. { product: 'socks', size: null },
  355. { product: 'trousers', size: ['S'] },
  356. { product: 'sweater', size: ['M', 'L'] }
  357. ]
  358. beforeAll(async () => {
  359. coll = await common.safeCollection(db, 'articles')
  360. const success = await coll.create(data)
  361. assert.strictEqual(success, true)
  362. })
  363. afterAll(async () => {
  364. const success = await coll.remove()
  365. assert.strictEqual(success, true)
  366. })
  367. it('解构', async () => {
  368. const result = await db
  369. .collection('articles')
  370. .aggregate()
  371. .unwind('$size')
  372. .project({
  373. _id: 0
  374. })
  375. .end()
  376. assert.strictEqual(result.data.length, 6)
  377. assert.deepStrictEqual(result.data[0], {
  378. product: 'tshirt',
  379. size: 'S'
  380. })
  381. })
  382. it('解构后,保留原数组索引', async () => {
  383. const result = await db
  384. .collection('articles')
  385. .aggregate()
  386. .unwind({
  387. path: '$size',
  388. includeArrayIndex: 'arrayIndex'
  389. })
  390. .project({
  391. _id: 0
  392. })
  393. .end()
  394. assert.strictEqual(result.data.length, 6)
  395. assert.deepStrictEqual(result.data[0], {
  396. arrayIndex: 0,
  397. product: 'tshirt',
  398. size: 'S'
  399. })
  400. })
  401. it('保留空值', async () => {
  402. const result = await db
  403. .collection('articles')
  404. .aggregate()
  405. .unwind({
  406. path: '$size',
  407. preserveNullAndEmptyArrays: true
  408. })
  409. .project({
  410. _id: 0
  411. })
  412. .end()
  413. assert.strictEqual(result.data.length, 8)
  414. assert.deepStrictEqual(result.data[0], {
  415. product: 'tshirt',
  416. size: 'S'
  417. })
  418. })
  419. })
  420. describe('Date', () => {
  421. let coll = null
  422. const date = new Date(1557826731686)
  423. const data = [{ date }]
  424. beforeAll(async () => {
  425. coll = await common.safeCollection(db, 'articles')
  426. const success = await coll.create(data)
  427. assert.strictEqual(success, true)
  428. const queryRes = await db
  429. .collection('articles')
  430. .where({})
  431. .get()
  432. // console.log('queryRes:', queryRes)
  433. })
  434. afterAll(async () => {
  435. const success = await coll.remove()
  436. assert.strictEqual(success, true)
  437. })
  438. it('Date类型', async () => {
  439. const result = await db
  440. .collection('articles')
  441. .aggregate()
  442. .project({
  443. _id: 0
  444. })
  445. .end()
  446. assert.deepStrictEqual(result.data[0], { date })
  447. })
  448. it('Date的各种操作符', async () => {
  449. const $ = db.command.aggregate
  450. const result = await db
  451. .collection('articles')
  452. .aggregate()
  453. .project({
  454. _id: 0,
  455. date: 1,
  456. dayOfWeek: $.dayOfWeek('$date'),
  457. dayOfYear: $.dayOfYear('$date'),
  458. dayOfMonth: $.dayOfMonth('$date'),
  459. year: $.year('$date'),
  460. month: $.month('$date'),
  461. hour: $.hour('$date'),
  462. minute: $.minute('$date'),
  463. second: $.second('$date'),
  464. millisecond: $.millisecond('$date'),
  465. week: $.week('$date'),
  466. dateFromParts: $.dateFromParts({
  467. year: 2017,
  468. month: 2,
  469. day: 8,
  470. hour: 12,
  471. timezone: 'America/New_York'
  472. }),
  473. dateFromString: $.dateFromString({
  474. dateString: date.toISOString()
  475. }),
  476. isoDayOfWeek: $.isoDayOfWeek('$date'),
  477. isoWeek: $.isoWeek('$date'),
  478. isoWeekYear: $.isoWeekYear('$date')
  479. })
  480. .end()
  481. assert.deepStrictEqual(result.data[0], {
  482. date,
  483. year: date.getFullYear(),
  484. month: date.getMonth() + 1,
  485. dayOfMonth: date.getDate(),
  486. hour: date.getUTCHours(),
  487. minute: date.getMinutes(),
  488. second: date.getSeconds(),
  489. millisecond: date.getMilliseconds(),
  490. dayOfYear: 134,
  491. dayOfWeek: 3,
  492. week: 19,
  493. dateFromParts: new Date('2017-02-08T17:00:00.000Z'),
  494. dateFromString: date,
  495. isoDayOfWeek: 2,
  496. isoWeek: 20,
  497. isoWeekYear: 2019
  498. })
  499. })
  500. })
  501. describe('lookup', () => {
  502. let coll1 = null
  503. let coll2 = null
  504. const data1 = [
  505. { name: 'stark', age: 24 },
  506. { name: 'justan', age: 24 },
  507. { name: 'jimmy', age: 24 }
  508. ]
  509. const data2 = [
  510. { name: 'stark', gender: 'male' },
  511. { name: 'justan', gender: 'male' },
  512. { name: 'jimmy', gender: 'male' }
  513. ]
  514. beforeAll(async () => {
  515. coll1 = await common.safeCollection(db, 'join1')
  516. coll2 = await common.safeCollection(db, 'join2')
  517. const success1 = await coll1.create(data1)
  518. const success2 = await coll2.create(data2)
  519. assert.strictEqual(success1, true)
  520. assert.strictEqual(success2, true)
  521. })
  522. afterAll(async () => {
  523. const success1 = await coll1.remove()
  524. assert.strictEqual(success1, true)
  525. const success2 = await coll2.remove()
  526. assert.strictEqual(success2, true)
  527. })
  528. it('lookup', async () => {
  529. const result = await db
  530. .collection('join1')
  531. .aggregate()
  532. .lookup({
  533. from: 'join2',
  534. localField: 'name',
  535. foreignField: 'name',
  536. as: 'join'
  537. })
  538. .end()
  539. assert(result.data[0].name === 'stark')
  540. assert(result.data[0].age === 24)
  541. assert(result.data[0].join[0].gender === 'male')
  542. })
  543. })
  544. describe('geoNear', () => {
  545. let coll1 = null
  546. const date = new Date()
  547. const data1 = [
  548. {
  549. _id: 'geoNear.0',
  550. city: 'Guangzhou',
  551. docType: 'geoNear',
  552. date: date,
  553. location: {
  554. type: 'Point',
  555. coordinates: [113.30593, 23.1361155]
  556. },
  557. name: 'Canton Tower'
  558. },
  559. {
  560. _id: 'geoNear.1',
  561. city: 'Hangzhou',
  562. docType: 'geoNear',
  563. date: new Date(date.getTime() - 1000),
  564. location: {
  565. type: 'Point',
  566. coordinates: [113.306789, 23.1564721]
  567. },
  568. name: 'Baiyun Mountain'
  569. },
  570. {
  571. _id: 'geoNear.2',
  572. city: 'Beijing',
  573. docType: 'geoNear',
  574. date: new Date(date.getTime() + 1000),
  575. location: {
  576. type: 'Point',
  577. coordinates: [116.3949659, 39.9163447]
  578. },
  579. name: 'The Palace Museum'
  580. },
  581. {
  582. _id: 'geoNear.3',
  583. city: 'Beijing',
  584. docType: 'geoNear',
  585. location: {
  586. type: 'Point',
  587. coordinates: [116.2328567, 40.242373]
  588. },
  589. name: 'Great Wall'
  590. }
  591. ]
  592. beforeAll(async () => {
  593. coll1 = await common.safeCollection(db, 'attractions')
  594. const success1 = await coll1.create(data1)
  595. assert.strictEqual(success1, true)
  596. })
  597. afterAll(async () => {
  598. const success1 = await coll1.remove()
  599. assert.strictEqual(success1, true)
  600. })
  601. it('geoNear', async () => {
  602. const $ = db.command.aggregate
  603. const _ = db.command
  604. const res = await db
  605. .collection('attractions')
  606. .aggregate()
  607. .geoNear({
  608. distanceField: 'distance', // 输出的每个记录中 distance 即是与给定点的距离
  609. spherical: true,
  610. near: new db.Geo.Point(113.3089506, 23.0968251),
  611. query: {
  612. city: /zhou/,
  613. date: _.lt(date)
  614. },
  615. key: 'location', // 若只有 location 一个地理位置索引的字段,则不需填
  616. includeLocs: 'location' // 若只有 location 一个是地理位置,则不需填
  617. })
  618. .end()
  619. assert.strictEqual(res.data.length === 1 && res.data[0].city === 'Hangzhou', true)
  620. })
  621. })