transaction.test.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  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 _ = db.command
  8. const offset = 60 * 1000
  9. describe('transaction', () => {
  10. let collection = null
  11. const collectionName = 'db-test-transactions'
  12. const date = new Date()
  13. const data = [
  14. {
  15. _id: '1',
  16. category: 'Web',
  17. tags: ['JavaScript', 'C#'],
  18. date,
  19. geo: new db.Geo.Point(90, 23),
  20. num: 2
  21. },
  22. { _id: '2', category: 'Web', tags: ['Go', 'C#'] },
  23. { _id: '3', category: 'Life', tags: ['Go', 'Python', 'JavaScript'] },
  24. { _id: '4', serverDate1: db.serverDate(), serverDate2: db.serverDate({ offset }) }
  25. ]
  26. beforeEach(async () => {
  27. collection = await common.safeCollection(db, collectionName)
  28. // 测试环境不稳定, 清除之前的影响
  29. await collection.remove()
  30. const success = await collection.create(data)
  31. assert.strictEqual(success, true)
  32. })
  33. afterEach(async () => {
  34. const success = await collection.remove()
  35. assert.strictEqual(success, true)
  36. })
  37. it('发起事务', async () => {
  38. const transaction = await db.startTransaction()
  39. assert.strictEqual(typeof transaction._id, 'string')
  40. })
  41. it('提交事务', async () => {
  42. const transaction = await db.startTransaction()
  43. const res = await transaction.commit()
  44. assert.strictEqual(typeof res.requestId, 'string')
  45. })
  46. it('runTransaction', async () => {
  47. await db.runTransaction(async function(transaction) {
  48. // 事务内插入 含date geo类型文档
  49. const insertDoc = { _id: 'lluke', date: date, geo: new db.Geo.Point(90, 23) }
  50. const insertRes = await transaction.collection(collectionName).add(insertDoc)
  51. assert(insertRes.inserted === 1)
  52. const doc = await transaction
  53. .collection(collectionName)
  54. .doc('lluke')
  55. .get()
  56. assert(doc.data, insertDoc)
  57. // 事务外插入含date geo serverDate 类型文档
  58. const doc1 = await transaction
  59. .collection(collectionName)
  60. .doc('1')
  61. .get()
  62. const doc4 = await transaction
  63. .collection(collectionName)
  64. .doc('4')
  65. .get()
  66. assert.deepStrictEqual(doc1.data, data[0])
  67. assert.deepStrictEqual(
  68. doc4.data.serverDate1.getTime() + offset === doc4.data.serverDate2.getTime(),
  69. true
  70. )
  71. // 事务内插入 含 serverDate 类型文档 报错不支持
  72. // const insertRes = await transaction.collection(collectionName).add({_id: 'lluke', serverDate1: db.serverDate(), serverDate2: db.serverDate({ offset })})
  73. // console.log('insertRes:', insertRes)
  74. // const lukeData = await transaction.collection(collectionName).doc('lluke').get()
  75. // assert(lukeData.data.serverDate1.getTime() + offset === lukeData.data.serverDate2.getTime(), true)
  76. })
  77. })
  78. it('事务内批量插入 条件查询', async () => {
  79. const transaction = await db.startTransaction()
  80. const addRes = await transaction
  81. .collection(collectionName)
  82. .add([{ name: 'a' }, { name: 'b' }])
  83. assert(addRes.ids.length === 2)
  84. const queryRes = await transaction
  85. .collection(collectionName)
  86. .where(_.or([{ name: 'a' }, { name: 'b' }]))
  87. .get()
  88. assert(queryRes.data.length === 2)
  89. const result = await transaction.commit()
  90. assert.strictEqual(typeof result.requestId, 'string')
  91. })
  92. it('事务内批量插入1000条文档', async () => {
  93. const transaction = await db.startTransaction()
  94. // 构造1001条数据
  95. const mockData = []
  96. let i = 0
  97. while (i++ <= 1000) {
  98. mockData.push({ name: `luketest${i}` })
  99. }
  100. const addRes = await transaction.collection(collectionName).add(mockData)
  101. assert(addRes.ids.length === 1001)
  102. const queryRes = await transaction
  103. .collection(collectionName)
  104. .where({ name: /^luketest/ })
  105. .limit(1000)
  106. .get()
  107. // console.log('queryRes:', queryRes)
  108. assert(queryRes.data.length === 1000)
  109. const result = await transaction.commit()
  110. assert.strictEqual(typeof result.requestId, 'string')
  111. })
  112. it('事务内更新含特殊类型 字段文档', async () => {
  113. await db.runTransaction(async function(transaction) {
  114. const newDate = new Date()
  115. const newGeo = new db.Geo.Point(90, 23)
  116. const updateRes = await transaction
  117. .collection(collectionName)
  118. .doc('1')
  119. .update({
  120. num: _.inc(1),
  121. date: newDate,
  122. geo: newGeo
  123. })
  124. assert(updateRes.updated === 1)
  125. const res = await transaction
  126. .collection(collectionName)
  127. .doc('1')
  128. .get()
  129. assert(res.data.num === 3, true)
  130. assert(res.data.date, newDate)
  131. assert(res.data.geo, newGeo)
  132. })
  133. })
  134. it('insert', async () => {
  135. const transaction = await db.startTransaction()
  136. const res = await transaction
  137. .collection(collectionName)
  138. .add({ category: 'Web', tags: ['JavaScript', 'C#'], date })
  139. assert(res.id !== undefined && res.inserted === 1)
  140. const result = await transaction.commit()
  141. assert.strictEqual(typeof result.requestId, 'string')
  142. })
  143. it('insert with custom docid', async () => {
  144. const docId = +new Date()
  145. const transaction = await db.startTransaction()
  146. const res = await transaction
  147. .collection(collectionName)
  148. .add({ _id: docId, category: 'Web', tags: ['JavaScript', 'C#'], date })
  149. assert(res.id == docId && res.inserted === 1)
  150. const deleteRes = await transaction
  151. .collection(collectionName)
  152. .doc(docId)
  153. .remove()
  154. assert(deleteRes.deleted === 1)
  155. const result = await transaction.commit()
  156. assert.strictEqual(typeof result.requestId, 'string')
  157. })
  158. it('get', async () => {
  159. // const docRef = db.collection(collectionName).doc('1')
  160. const transaction = await db.startTransaction()
  161. // const doc = await transaction.get(docRef)
  162. const doc = await transaction
  163. .collection(collectionName)
  164. .doc('1')
  165. .get()
  166. assert.deepStrictEqual(doc.data, data[0])
  167. const res = await transaction.commit()
  168. assert(res.requestId)
  169. })
  170. it('get不存在的文档,返回null', async () => {
  171. // const docRef = db.collection(collectionName).doc('114514')
  172. const transaction = await db.startTransaction()
  173. // const doc = await transaction.get(docRef)
  174. const doc = await transaction
  175. .collection(collectionName)
  176. .doc('114514')
  177. .get()
  178. assert.strictEqual(doc.data, null)
  179. const res = await transaction.commit()
  180. assert(res.requestId)
  181. })
  182. it('事务回滚', async () => {
  183. const transaction = await db.startTransaction()
  184. // const docRef = db.collection(collectionName).doc('1')
  185. // const doc = await transaction.get(docRef)
  186. const doc = await transaction
  187. .collection(collectionName)
  188. .doc('1')
  189. .get()
  190. const addDoc = await transaction
  191. .collection(collectionName)
  192. .add({ _id: '5', category: 'hope' })
  193. assert.deepStrictEqual(addDoc.id, '5')
  194. assert.deepStrictEqual(doc.data, data[0])
  195. const res = await transaction.rollback()
  196. assert(res.requestId)
  197. })
  198. it('update', async () => {
  199. // const docRef = db.collection(collectionName).doc('1')
  200. const transaction = await db.startTransaction()
  201. // let doc = await transaction.get(docRef)
  202. let doc = await transaction
  203. .collection(collectionName)
  204. .doc('1')
  205. .get()
  206. assert.deepStrictEqual(doc.data, data[0])
  207. const date = new Date()
  208. // const updateResult = await transaction.update(docRef, {
  209. // category: 'Node.js',
  210. // date
  211. // })
  212. const updateResult = await transaction
  213. .collection(collectionName)
  214. .doc('1')
  215. .update({
  216. category: 'Node.js',
  217. date
  218. })
  219. assert.strictEqual(updateResult.updated, 1)
  220. // doc = await transaction.get(docRef)
  221. doc = await transaction
  222. .collection(collectionName)
  223. .doc('1')
  224. .get()
  225. assert.deepStrictEqual(doc.data, {
  226. ...data[0],
  227. date,
  228. category: 'Node.js'
  229. })
  230. const res = await transaction.commit()
  231. assert(res.requestId)
  232. })
  233. it('modifyAndReturnDoc', async () => {
  234. const transaction = await db.startTransaction()
  235. let modifyAndReturnRes = await transaction
  236. .collection(collectionName)
  237. .where({ category: 'Web' })
  238. .updateAndReturn({
  239. category: 'web'
  240. })
  241. const res = await transaction.commit()
  242. assert(modifyAndReturnRes.doc.category === 'web')
  243. })
  244. it('set doc', async () => {
  245. // const docRef = db.collection(collectionName).doc('1')
  246. const transaction = await db.startTransaction()
  247. // let doc = await transaction.get(docRef)
  248. let doc = await transaction
  249. .collection(collectionName)
  250. .doc('1')
  251. .get()
  252. const date = new Date()
  253. // const updateResult = await transaction.set(docRef, {
  254. // ...data[0],
  255. // date,
  256. // category: 'Node.js'
  257. // })
  258. const newData = { ...data[0] }
  259. delete newData['_id']
  260. const updateResult = await transaction
  261. .collection(collectionName)
  262. .doc('1')
  263. .set({
  264. ...newData,
  265. date,
  266. category: 'Node.js'
  267. })
  268. assert.strictEqual(updateResult.updated, 1)
  269. // doc = await transaction.get(docRef)
  270. doc = await transaction
  271. .collection(collectionName)
  272. .doc('1')
  273. .get()
  274. assert.deepStrictEqual(doc.data, {
  275. ...data[0],
  276. date,
  277. category: 'Node.js'
  278. })
  279. const res = await transaction.commit()
  280. assert(res.requestId)
  281. })
  282. it('upsert doc', async () => {
  283. // const docRef = db.collection(collectionName).doc('114514')
  284. const transaction = await db.startTransaction()
  285. // let doc = await transaction.get(docRef)
  286. let doc = await transaction
  287. .collection(collectionName)
  288. .doc('114514')
  289. .get()
  290. assert.deepStrictEqual(doc.data, null)
  291. const date = new Date()
  292. const data = {
  293. category: 'Node.js',
  294. date
  295. }
  296. // const updateResult = await transaction.set(docRef, data)
  297. const updateResult = await transaction
  298. .collection(collectionName)
  299. .doc('114514')
  300. .set(data)
  301. assert.strictEqual(updateResult.upserted.length, 1)
  302. // doc = await transaction.get(docRef)
  303. doc = await transaction
  304. .collection(collectionName)
  305. .doc('114514')
  306. .get()
  307. assert.deepStrictEqual(doc.data, {
  308. _id: '114514',
  309. ...data
  310. })
  311. const res = await transaction.rollback()
  312. assert(res.requestId)
  313. })
  314. it('delete doc', async () => {
  315. // 前面测试用例更改过 _id = 1 的数据
  316. // const docRef = db.collection(collectionName).doc('2')
  317. const transaction = await db.startTransaction()
  318. // let doc = await transaction.get(docRef)
  319. let doc = await transaction
  320. .collection(collectionName)
  321. .doc('2')
  322. .get()
  323. assert.deepStrictEqual(doc.data, data[1])
  324. // const deleteResult = await transaction.delete(docRef)
  325. const deleteResult = await transaction
  326. .collection(collectionName)
  327. .doc('2')
  328. .delete()
  329. assert.strictEqual(deleteResult.deleted, 1)
  330. // doc = await transaction.get(docRef)
  331. doc = await transaction
  332. .collection(collectionName)
  333. .doc('2')
  334. .get()
  335. assert.deepStrictEqual(doc.data, null)
  336. await transaction.commit()
  337. })
  338. it('runTransaction with customResult', async () => {
  339. // 验证自定义成功返回
  340. const result = await db.runTransaction(async function(transaction) {
  341. const doc = await transaction
  342. .collection(collectionName)
  343. .doc('1')
  344. .get()
  345. assert.deepStrictEqual(doc.data, data[0])
  346. // assert(doc.data)
  347. return 'luke'
  348. })
  349. assert(result === 'luke')
  350. })
  351. // runTransaction rollback
  352. it('rollback within runTransaction', async () => {
  353. try {
  354. await db.runTransaction(async function(transaction) {
  355. const doc = await transaction
  356. .collection(collectionName)
  357. .doc('1')
  358. .get()
  359. assert.deepStrictEqual(doc.data, data[0])
  360. await transaction.rollback('luke')
  361. })
  362. } catch (err) {
  363. assert(err === 'luke')
  364. }
  365. try {
  366. await db.runTransaction(async function(transaction) {
  367. const doc = await transaction
  368. .collection(collectionName)
  369. .doc('1')
  370. .get()
  371. assert.deepStrictEqual(doc.data, data[0])
  372. await transaction.rollback()
  373. })
  374. } catch (err) {
  375. assert(err === undefined)
  376. }
  377. try {
  378. await db.runTransaction(async transaction => {
  379. const doc = await transaction
  380. .collection(collectionName)
  381. .doc('1')
  382. .get()
  383. assert.deepStrictEqual(doc.data, data[0])
  384. // mock 事务冲突
  385. throw {
  386. code: 'DATABASE_TRANSACTION_CONFLICT',
  387. message:
  388. '[ResourceUnavailable.TransactionConflict] Transaction is conflict, maybe resource operated by others. Please check your request, but if the problem persists, contact us.'
  389. }
  390. })
  391. } catch (e) {
  392. assert(e.code === 'DATABASE_TRANSACTION_CONFLICT')
  393. }
  394. try {
  395. const docRef1 = db.collection(collectionName).doc('1')
  396. await db.runTransaction(async transaction => {
  397. const doc = await transaction
  398. .collection(collectionName)
  399. .doc('1')
  400. .get()
  401. await docRef1.set({
  402. category: 'wwwwwwwwwwwwwwwww'
  403. })
  404. const res = await transaction
  405. .collection(collectionName)
  406. .doc('1')
  407. .update({
  408. category: 'transactiontransactiontransaction'
  409. })
  410. })
  411. } catch (e) {
  412. // 因为mongo write conflict时会自动rollback,兜底的rollback会报错 非conflict错误
  413. assert(e.code)
  414. // assert(e.code === 'DATABASE_TRANSACTION_CONFLICT')
  415. }
  416. })
  417. it('delete doc and abort', async () => {
  418. // 前面测试用例删除了 _id = 2 的数据
  419. const docRef = db.collection(collectionName).doc('3')
  420. const transaction = await db.startTransaction()
  421. // let doc = await transaction.get(docRef)
  422. let doc = await transaction
  423. .collection(collectionName)
  424. .doc('3')
  425. .get()
  426. // const deleteResult = await transaction.delete(docRef)
  427. const deleteResult = await transaction
  428. .collection(collectionName)
  429. .doc('3')
  430. .delete()
  431. assert.strictEqual(deleteResult.deleted, 1)
  432. // doc = await transaction.get(docRef)
  433. doc = await transaction
  434. .collection(collectionName)
  435. .doc('3')
  436. .get()
  437. assert.deepStrictEqual(doc.data, null)
  438. await transaction.rollback()
  439. const res = await docRef.get()
  440. // const res = await transaction.collection(collectionName).doc('3').get()
  441. assert.deepStrictEqual(res.data[0], data[2])
  442. })
  443. it('事务提交后, 不能进行其它操作', async () => {
  444. // const docRef = db.collection(collectionName).doc('1')
  445. const transaction = await db.startTransaction()
  446. await transaction.commit()
  447. await assert.rejects(
  448. async () => {
  449. // await transaction.set(docRef, {
  450. // category: 'Node.js'
  451. // })
  452. await transaction
  453. .collection(collectionName)
  454. .doc('1')
  455. .set({
  456. category: 'Node.js'
  457. })
  458. },
  459. {
  460. code: 'DATABASE_TRANSACTION_FAIL',
  461. message:
  462. '[ResourceUnavailable.TransactionNotExist] Transaction does not exist on the server, transaction must be commit or abort in 30 seconds. Please check your request, but if the problem persists, contact us.'
  463. }
  464. )
  465. })
  466. it('冲突检测', async () => {
  467. // const docRef = db.collection(collectionName).doc('1')
  468. const transaction1 = await db.startTransaction(),
  469. transaction2 = await db.startTransaction()
  470. // 事务1先读取数据
  471. // const doc = await transaction1.get(docRef)
  472. const doc = await transaction1
  473. .collection(collectionName)
  474. .doc('1')
  475. .get()
  476. // 事务2更改之前事务1读取的数据,并且提交
  477. // await transaction2.update(docRef, {
  478. // category: '冲突检测'
  479. // })
  480. await transaction2
  481. .collection(collectionName)
  482. .doc('1')
  483. .update({
  484. category: '冲突检测'
  485. })
  486. // 由于事务1读取的数据没有时效性,故报错
  487. await assert.rejects(
  488. async () => {
  489. // await transaction1.update(docRef, {
  490. // category: doc.data.category + '冲突检测'
  491. // })
  492. await transaction1
  493. .collection(collectionName)
  494. .doc('1')
  495. .update({
  496. category: doc.data.category + '冲突检测'
  497. })
  498. },
  499. {
  500. code: 'DATABASE_TRANSACTION_CONFLICT'
  501. }
  502. )
  503. await transaction2.commit()
  504. })
  505. it('读快照', async () => {
  506. const docRef = db.collection(collectionName).doc('1')
  507. // 启动事务
  508. const transaction = await db.startTransaction()
  509. // 修改数据
  510. // await transaction.update(docRef, {
  511. // category: 'update in transaction'
  512. // })
  513. await transaction
  514. .collection(collectionName)
  515. .doc('1')
  516. .update({
  517. category: 'update in transaction'
  518. })
  519. const result = await docRef.get()
  520. // 事务读,读的是开始时刻的快照
  521. // const doc_new = await transaction.get(docRef)
  522. const doc_new = await transaction
  523. .collection(collectionName)
  524. .doc('1')
  525. .get()
  526. assert.deepStrictEqual(result.data[0], data[0])
  527. assert.deepStrictEqual(doc_new.data, {
  528. ...data[0],
  529. category: 'update in transaction'
  530. })
  531. await transaction.rollback()
  532. })
  533. it('读偏', async () => {
  534. const docRef1 = db.collection(collectionName).doc('1')
  535. const docRef2 = db.collection(collectionName).doc('2')
  536. // 启动事务
  537. const transaction = await db.startTransaction()
  538. // 修改数据
  539. // console.log(await transaction.get(docRef1))
  540. await transaction
  541. .collection(collectionName)
  542. .doc('1')
  543. .get()
  544. await docRef1.set({
  545. category: 'wwwwwwwwwwwwwwwww'
  546. })
  547. await docRef2.set({
  548. category: 'hhhhhhhhhhhhhhh'
  549. })
  550. // 事务读,读的是开始时刻的快照
  551. // const snapshot1 = await transaction.get(docRef1)
  552. // const snapshot2 = await transaction.get(docRef2)
  553. const snapshot1 = await transaction
  554. .collection(collectionName)
  555. .doc('1')
  556. .get()
  557. const snapshot2 = await transaction
  558. .collection(collectionName)
  559. .doc('2')
  560. .get()
  561. assert.deepStrictEqual(snapshot1.data, data[0])
  562. assert.deepStrictEqual(snapshot2.data, data[1])
  563. // 外部已经修改了数据,事务内修改应该失败
  564. await assert.rejects(
  565. async () => {
  566. // await transaction.update(docRef1, {
  567. // category: 'transactiontransactiontransaction'
  568. // })
  569. await transaction
  570. .collection(collectionName)
  571. .doc('1')
  572. .update({
  573. category: 'transactiontransactiontransaction'
  574. })
  575. },
  576. {
  577. code: 'DATABASE_TRANSACTION_CONFLICT',
  578. message:
  579. '[ResourceUnavailable.TransactionConflict] Transaction is conflict, maybe resource operated by others. Please check your request, but if the problem persists, contact us.'
  580. }
  581. )
  582. })
  583. it('write skew', async () => {
  584. const docRef1 = db.collection(collectionName).doc('1')
  585. const docRef2 = db.collection(collectionName).doc('2')
  586. const transaction1 = await db.startTransaction()
  587. const transaction2 = await db.startTransaction()
  588. // 事务1:读1写2
  589. // 事务2:读2写1
  590. // const doc1 = await transaction1.get(docRef1)
  591. // const doc2 = await transaction2.get(docRef2)
  592. const doc1 = await transaction1
  593. .collection(collectionName)
  594. .doc('1')
  595. .get()
  596. const doc2 = await transaction2
  597. .collection(collectionName)
  598. .doc('2')
  599. .get()
  600. // await transaction1.set(docRef2, {
  601. // category: doc1.data.category + 'doc1'
  602. // })
  603. // await transaction2.set(docRef1, {
  604. // category: doc2.data.category + 'doc2'
  605. // })
  606. await transaction1
  607. .collection(collectionName)
  608. .doc('2')
  609. .set({
  610. category: doc1.data.category + 'doc1'
  611. })
  612. await transaction2
  613. .collection(collectionName)
  614. .doc('1')
  615. .set({
  616. category: doc2.data.category + 'doc2'
  617. })
  618. // 由于事务2读取的数据没有时效性,故报错
  619. try {
  620. await transaction1.commit()
  621. await transaction2.commit()
  622. } catch (error) {
  623. console.log(error)
  624. }
  625. console.log(await docRef1.get())
  626. console.log(await docRef2.get())
  627. })
  628. })