123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- import * as assert from 'power-assert'
- import tcb from '../../../src/index'
- import * as Config from '../../config.local'
- import * as common from '../../common/index'
- const app = tcb.init(Config)
- const db = app.database()
- const _ = db.command
- const offset = 60 * 1000
- describe('transaction', () => {
- let collection = null
- const collectionName = 'db-test-transactions'
- const date = new Date()
- const data = [
- {
- _id: '1',
- category: 'Web',
- tags: ['JavaScript', 'C#'],
- date,
- geo: new db.Geo.Point(90, 23),
- num: 2
- },
- { _id: '2', category: 'Web', tags: ['Go', 'C#'] },
- { _id: '3', category: 'Life', tags: ['Go', 'Python', 'JavaScript'] },
- { _id: '4', serverDate1: db.serverDate(), serverDate2: db.serverDate({ offset }) }
- ]
- beforeEach(async () => {
- collection = await common.safeCollection(db, collectionName)
- // 测试环境不稳定, 清除之前的影响
- await collection.remove()
- const success = await collection.create(data)
- assert.strictEqual(success, true)
- })
- afterEach(async () => {
- const success = await collection.remove()
- assert.strictEqual(success, true)
- })
- it('发起事务', async () => {
- const transaction = await db.startTransaction()
- assert.strictEqual(typeof transaction._id, 'string')
- })
- it('提交事务', async () => {
- const transaction = await db.startTransaction()
- const res = await transaction.commit()
- assert.strictEqual(typeof res.requestId, 'string')
- })
- it('runTransaction', async () => {
- await db.runTransaction(async function(transaction) {
- // 事务内插入 含date geo类型文档
- const insertDoc = { _id: 'lluke', date: date, geo: new db.Geo.Point(90, 23) }
- const insertRes = await transaction.collection(collectionName).add(insertDoc)
- assert(insertRes.inserted === 1)
- const doc = await transaction
- .collection(collectionName)
- .doc('lluke')
- .get()
- assert(doc.data, insertDoc)
- // 事务外插入含date geo serverDate 类型文档
- const doc1 = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- const doc4 = await transaction
- .collection(collectionName)
- .doc('4')
- .get()
- assert.deepStrictEqual(doc1.data, data[0])
- assert.deepStrictEqual(
- doc4.data.serverDate1.getTime() + offset === doc4.data.serverDate2.getTime(),
- true
- )
- // 事务内插入 含 serverDate 类型文档 报错不支持
- // const insertRes = await transaction.collection(collectionName).add({_id: 'lluke', serverDate1: db.serverDate(), serverDate2: db.serverDate({ offset })})
- // console.log('insertRes:', insertRes)
- // const lukeData = await transaction.collection(collectionName).doc('lluke').get()
- // assert(lukeData.data.serverDate1.getTime() + offset === lukeData.data.serverDate2.getTime(), true)
- })
- })
- it('事务内批量插入 条件查询', async () => {
- const transaction = await db.startTransaction()
- const addRes = await transaction
- .collection(collectionName)
- .add([{ name: 'a' }, { name: 'b' }])
- assert(addRes.ids.length === 2)
- const queryRes = await transaction
- .collection(collectionName)
- .where(_.or([{ name: 'a' }, { name: 'b' }]))
- .get()
- assert(queryRes.data.length === 2)
- const result = await transaction.commit()
- assert.strictEqual(typeof result.requestId, 'string')
- })
- it('事务内批量插入1000条文档', async () => {
- const transaction = await db.startTransaction()
- // 构造1001条数据
- const mockData = []
- let i = 0
- while (i++ <= 1000) {
- mockData.push({ name: `luketest${i}` })
- }
- const addRes = await transaction.collection(collectionName).add(mockData)
- assert(addRes.ids.length === 1001)
- const queryRes = await transaction
- .collection(collectionName)
- .where({ name: /^luketest/ })
- .limit(1000)
- .get()
- // console.log('queryRes:', queryRes)
- assert(queryRes.data.length === 1000)
- const result = await transaction.commit()
- assert.strictEqual(typeof result.requestId, 'string')
- })
- it('事务内更新含特殊类型 字段文档', async () => {
- await db.runTransaction(async function(transaction) {
- const newDate = new Date()
- const newGeo = new db.Geo.Point(90, 23)
- const updateRes = await transaction
- .collection(collectionName)
- .doc('1')
- .update({
- num: _.inc(1),
- date: newDate,
- geo: newGeo
- })
- assert(updateRes.updated === 1)
- const res = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert(res.data.num === 3, true)
- assert(res.data.date, newDate)
- assert(res.data.geo, newGeo)
- })
- })
- it('insert', async () => {
- const transaction = await db.startTransaction()
- const res = await transaction
- .collection(collectionName)
- .add({ category: 'Web', tags: ['JavaScript', 'C#'], date })
- assert(res.id !== undefined && res.inserted === 1)
- const result = await transaction.commit()
- assert.strictEqual(typeof result.requestId, 'string')
- })
- it('insert with custom docid', async () => {
- const docId = +new Date()
- const transaction = await db.startTransaction()
- const res = await transaction
- .collection(collectionName)
- .add({ _id: docId, category: 'Web', tags: ['JavaScript', 'C#'], date })
- assert(res.id == docId && res.inserted === 1)
- const deleteRes = await transaction
- .collection(collectionName)
- .doc(docId)
- .remove()
- assert(deleteRes.deleted === 1)
- const result = await transaction.commit()
- assert.strictEqual(typeof result.requestId, 'string')
- })
- it('get', async () => {
- // const docRef = db.collection(collectionName).doc('1')
- const transaction = await db.startTransaction()
- // const doc = await transaction.get(docRef)
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- const res = await transaction.commit()
- assert(res.requestId)
- })
- it('get不存在的文档,返回null', async () => {
- // const docRef = db.collection(collectionName).doc('114514')
- const transaction = await db.startTransaction()
- // const doc = await transaction.get(docRef)
- const doc = await transaction
- .collection(collectionName)
- .doc('114514')
- .get()
- assert.strictEqual(doc.data, null)
- const res = await transaction.commit()
- assert(res.requestId)
- })
- it('事务回滚', async () => {
- const transaction = await db.startTransaction()
- // const docRef = db.collection(collectionName).doc('1')
- // const doc = await transaction.get(docRef)
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- const addDoc = await transaction
- .collection(collectionName)
- .add({ _id: '5', category: 'hope' })
- assert.deepStrictEqual(addDoc.id, '5')
- assert.deepStrictEqual(doc.data, data[0])
- const res = await transaction.rollback()
- assert(res.requestId)
- })
- it('update', async () => {
- // const docRef = db.collection(collectionName).doc('1')
- const transaction = await db.startTransaction()
- // let doc = await transaction.get(docRef)
- let doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- const date = new Date()
- // const updateResult = await transaction.update(docRef, {
- // category: 'Node.js',
- // date
- // })
- const updateResult = await transaction
- .collection(collectionName)
- .doc('1')
- .update({
- category: 'Node.js',
- date
- })
- assert.strictEqual(updateResult.updated, 1)
- // doc = await transaction.get(docRef)
- doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, {
- ...data[0],
- date,
- category: 'Node.js'
- })
- const res = await transaction.commit()
- assert(res.requestId)
- })
- it('modifyAndReturnDoc', async () => {
- const transaction = await db.startTransaction()
- let modifyAndReturnRes = await transaction
- .collection(collectionName)
- .where({ category: 'Web' })
- .updateAndReturn({
- category: 'web'
- })
- const res = await transaction.commit()
- assert(modifyAndReturnRes.doc.category === 'web')
- })
- it('set doc', async () => {
- // const docRef = db.collection(collectionName).doc('1')
- const transaction = await db.startTransaction()
- // let doc = await transaction.get(docRef)
- let doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- const date = new Date()
- // const updateResult = await transaction.set(docRef, {
- // ...data[0],
- // date,
- // category: 'Node.js'
- // })
- const newData = { ...data[0] }
- delete newData['_id']
- const updateResult = await transaction
- .collection(collectionName)
- .doc('1')
- .set({
- ...newData,
- date,
- category: 'Node.js'
- })
- assert.strictEqual(updateResult.updated, 1)
- // doc = await transaction.get(docRef)
- doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, {
- ...data[0],
- date,
- category: 'Node.js'
- })
- const res = await transaction.commit()
- assert(res.requestId)
- })
- it('upsert doc', async () => {
- // const docRef = db.collection(collectionName).doc('114514')
- const transaction = await db.startTransaction()
- // let doc = await transaction.get(docRef)
- let doc = await transaction
- .collection(collectionName)
- .doc('114514')
- .get()
- assert.deepStrictEqual(doc.data, null)
- const date = new Date()
- const data = {
- category: 'Node.js',
- date
- }
- // const updateResult = await transaction.set(docRef, data)
- const updateResult = await transaction
- .collection(collectionName)
- .doc('114514')
- .set(data)
- assert.strictEqual(updateResult.upserted.length, 1)
- // doc = await transaction.get(docRef)
- doc = await transaction
- .collection(collectionName)
- .doc('114514')
- .get()
- assert.deepStrictEqual(doc.data, {
- _id: '114514',
- ...data
- })
- const res = await transaction.rollback()
- assert(res.requestId)
- })
- it('delete doc', async () => {
- // 前面测试用例更改过 _id = 1 的数据
- // const docRef = db.collection(collectionName).doc('2')
- const transaction = await db.startTransaction()
- // let doc = await transaction.get(docRef)
- let doc = await transaction
- .collection(collectionName)
- .doc('2')
- .get()
- assert.deepStrictEqual(doc.data, data[1])
- // const deleteResult = await transaction.delete(docRef)
- const deleteResult = await transaction
- .collection(collectionName)
- .doc('2')
- .delete()
- assert.strictEqual(deleteResult.deleted, 1)
- // doc = await transaction.get(docRef)
- doc = await transaction
- .collection(collectionName)
- .doc('2')
- .get()
- assert.deepStrictEqual(doc.data, null)
- await transaction.commit()
- })
- it('runTransaction with customResult', async () => {
- // 验证自定义成功返回
- const result = await db.runTransaction(async function(transaction) {
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- // assert(doc.data)
- return 'luke'
- })
- assert(result === 'luke')
- })
- // runTransaction rollback
- it('rollback within runTransaction', async () => {
- try {
- await db.runTransaction(async function(transaction) {
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- await transaction.rollback('luke')
- })
- } catch (err) {
- assert(err === 'luke')
- }
- try {
- await db.runTransaction(async function(transaction) {
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- await transaction.rollback()
- })
- } catch (err) {
- assert(err === undefined)
- }
- try {
- await db.runTransaction(async transaction => {
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(doc.data, data[0])
- // mock 事务冲突
- throw {
- code: 'DATABASE_TRANSACTION_CONFLICT',
- message:
- '[ResourceUnavailable.TransactionConflict] Transaction is conflict, maybe resource operated by others. Please check your request, but if the problem persists, contact us.'
- }
- })
- } catch (e) {
- assert(e.code === 'DATABASE_TRANSACTION_CONFLICT')
- }
- try {
- const docRef1 = db.collection(collectionName).doc('1')
- await db.runTransaction(async transaction => {
- const doc = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- await docRef1.set({
- category: 'wwwwwwwwwwwwwwwww'
- })
- const res = await transaction
- .collection(collectionName)
- .doc('1')
- .update({
- category: 'transactiontransactiontransaction'
- })
- })
- } catch (e) {
- // 因为mongo write conflict时会自动rollback,兜底的rollback会报错 非conflict错误
- assert(e.code)
- // assert(e.code === 'DATABASE_TRANSACTION_CONFLICT')
- }
- })
- it('delete doc and abort', async () => {
- // 前面测试用例删除了 _id = 2 的数据
- const docRef = db.collection(collectionName).doc('3')
- const transaction = await db.startTransaction()
- // let doc = await transaction.get(docRef)
- let doc = await transaction
- .collection(collectionName)
- .doc('3')
- .get()
- // const deleteResult = await transaction.delete(docRef)
- const deleteResult = await transaction
- .collection(collectionName)
- .doc('3')
- .delete()
- assert.strictEqual(deleteResult.deleted, 1)
- // doc = await transaction.get(docRef)
- doc = await transaction
- .collection(collectionName)
- .doc('3')
- .get()
- assert.deepStrictEqual(doc.data, null)
- await transaction.rollback()
- const res = await docRef.get()
- // const res = await transaction.collection(collectionName).doc('3').get()
- assert.deepStrictEqual(res.data[0], data[2])
- })
- it('事务提交后, 不能进行其它操作', async () => {
- // const docRef = db.collection(collectionName).doc('1')
- const transaction = await db.startTransaction()
- await transaction.commit()
- await assert.rejects(
- async () => {
- // await transaction.set(docRef, {
- // category: 'Node.js'
- // })
- await transaction
- .collection(collectionName)
- .doc('1')
- .set({
- category: 'Node.js'
- })
- },
- {
- code: 'DATABASE_TRANSACTION_FAIL',
- message:
- '[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.'
- }
- )
- })
- it('冲突检测', async () => {
- // const docRef = db.collection(collectionName).doc('1')
- const transaction1 = await db.startTransaction(),
- transaction2 = await db.startTransaction()
- // 事务1先读取数据
- // const doc = await transaction1.get(docRef)
- const doc = await transaction1
- .collection(collectionName)
- .doc('1')
- .get()
- // 事务2更改之前事务1读取的数据,并且提交
- // await transaction2.update(docRef, {
- // category: '冲突检测'
- // })
- await transaction2
- .collection(collectionName)
- .doc('1')
- .update({
- category: '冲突检测'
- })
- // 由于事务1读取的数据没有时效性,故报错
- await assert.rejects(
- async () => {
- // await transaction1.update(docRef, {
- // category: doc.data.category + '冲突检测'
- // })
- await transaction1
- .collection(collectionName)
- .doc('1')
- .update({
- category: doc.data.category + '冲突检测'
- })
- },
- {
- code: 'DATABASE_TRANSACTION_CONFLICT'
- }
- )
- await transaction2.commit()
- })
- it('读快照', async () => {
- const docRef = db.collection(collectionName).doc('1')
- // 启动事务
- const transaction = await db.startTransaction()
- // 修改数据
- // await transaction.update(docRef, {
- // category: 'update in transaction'
- // })
- await transaction
- .collection(collectionName)
- .doc('1')
- .update({
- category: 'update in transaction'
- })
- const result = await docRef.get()
- // 事务读,读的是开始时刻的快照
- // const doc_new = await transaction.get(docRef)
- const doc_new = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- assert.deepStrictEqual(result.data[0], data[0])
- assert.deepStrictEqual(doc_new.data, {
- ...data[0],
- category: 'update in transaction'
- })
- await transaction.rollback()
- })
- it('读偏', async () => {
- const docRef1 = db.collection(collectionName).doc('1')
- const docRef2 = db.collection(collectionName).doc('2')
- // 启动事务
- const transaction = await db.startTransaction()
- // 修改数据
- // console.log(await transaction.get(docRef1))
- await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- await docRef1.set({
- category: 'wwwwwwwwwwwwwwwww'
- })
- await docRef2.set({
- category: 'hhhhhhhhhhhhhhh'
- })
- // 事务读,读的是开始时刻的快照
- // const snapshot1 = await transaction.get(docRef1)
- // const snapshot2 = await transaction.get(docRef2)
- const snapshot1 = await transaction
- .collection(collectionName)
- .doc('1')
- .get()
- const snapshot2 = await transaction
- .collection(collectionName)
- .doc('2')
- .get()
- assert.deepStrictEqual(snapshot1.data, data[0])
- assert.deepStrictEqual(snapshot2.data, data[1])
- // 外部已经修改了数据,事务内修改应该失败
- await assert.rejects(
- async () => {
- // await transaction.update(docRef1, {
- // category: 'transactiontransactiontransaction'
- // })
- await transaction
- .collection(collectionName)
- .doc('1')
- .update({
- category: 'transactiontransactiontransaction'
- })
- },
- {
- code: 'DATABASE_TRANSACTION_CONFLICT',
- message:
- '[ResourceUnavailable.TransactionConflict] Transaction is conflict, maybe resource operated by others. Please check your request, but if the problem persists, contact us.'
- }
- )
- })
- it('write skew', async () => {
- const docRef1 = db.collection(collectionName).doc('1')
- const docRef2 = db.collection(collectionName).doc('2')
- const transaction1 = await db.startTransaction()
- const transaction2 = await db.startTransaction()
- // 事务1:读1写2
- // 事务2:读2写1
- // const doc1 = await transaction1.get(docRef1)
- // const doc2 = await transaction2.get(docRef2)
- const doc1 = await transaction1
- .collection(collectionName)
- .doc('1')
- .get()
- const doc2 = await transaction2
- .collection(collectionName)
- .doc('2')
- .get()
- // await transaction1.set(docRef2, {
- // category: doc1.data.category + 'doc1'
- // })
- // await transaction2.set(docRef1, {
- // category: doc2.data.category + 'doc2'
- // })
- await transaction1
- .collection(collectionName)
- .doc('2')
- .set({
- category: doc1.data.category + 'doc1'
- })
- await transaction2
- .collection(collectionName)
- .doc('1')
- .set({
- category: doc2.data.category + 'doc2'
- })
- // 由于事务2读取的数据没有时效性,故报错
- try {
- await transaction1.commit()
- await transaction2.commit()
- } catch (error) {
- console.log(error)
- }
- console.log(await docRef1.get())
- console.log(await docRef2.get())
- })
- })
|