w-orm-reladb 1.0.47 → 1.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -50
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +758 -73
- package/docs/WOrmReladb.mjs.html +869 -299
- package/docs/index.html +2 -2
- package/g-genModelsByTabs.mjs +1 -1
- package/g-mssql-concurrency.mjs +1 -1
- package/g-mssql-timerQuery.mjs +3 -3
- package/g-mssql-transaction-commit.mjs +7 -7
- package/g-mssql-transaction-rollback.mjs +7 -7
- package/g-mssql.mjs +7 -7
- package/g-sqlite-concurrency.mjs +1 -1
- package/g-sqlite-encryption-transaction-commit.mjs +3 -3
- package/g-sqlite-encryption.mjs +7 -7
- package/g-sqlite-transaction-commit.mjs +7 -7
- package/g-sqlite-transaction-rollback.mjs +7 -7
- package/g-sqlite.mjs +7 -7
- package/package.json +1 -1
- package/script.txt +0 -1
- package/src/WOrmReladb.mjs +867 -297
- package/test/api-mssql.test.mjs +4 -201
- package/test/api-sqlite.test.mjs +12 -206
- package/test/api-suite.mjs +729 -0
- package/worm.sqlite +0 -0
- package/test/api-fixtures.mjs +0 -48
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import wo from '../src/WOrmReladb.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//本檔為mssql與sqlite共用之測試套組, 令兩種後端以同一組斷言驗證, 避免各自漂移
|
|
6
|
+
//斷言皆為規格(資料庫函數回傳定義計算.md)之可執行翻譯, 非現況指紋
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
//genRs, 初始插入數據
|
|
10
|
+
export let genRs = () => {
|
|
11
|
+
return [
|
|
12
|
+
{ id: 'id-peter', name: 'peter', value: 123 },
|
|
13
|
+
{ id: 'id-rosemary', name: 'rosemary', value: 123.456 },
|
|
14
|
+
{ id: '', name: 'kettle', value: 456 },
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
//genRsm, 儲存(修改)數據, 第3筆id為空字串需視autoInsert決定是否插入
|
|
20
|
+
export let genRsm = () => {
|
|
21
|
+
return [
|
|
22
|
+
{ id: 'id-peter', name: 'peter(modify)' },
|
|
23
|
+
{ id: 'id-rosemary', name: 'rosemary(modify)' },
|
|
24
|
+
{ id: '', name: 'kettle(modify)' },
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
//sortByName, select結果排序供穩定比對
|
|
30
|
+
export let sortByName = (rows) => {
|
|
31
|
+
return [...rows].sort((a, b) => (a.name < b.name ? -1 : 1))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 定義共用測試套組
|
|
37
|
+
*
|
|
38
|
+
* @param {String} label 輸入後端名稱字串, 供describe標題使用
|
|
39
|
+
* @param {Function} getOpt 輸入取得建構設定之函數, 於hook內呼叫以取得當下有效之連線資訊
|
|
40
|
+
*/
|
|
41
|
+
export function defineSuites(label, getOpt) {
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
describe(`${label} crud`, function() {
|
|
45
|
+
|
|
46
|
+
let w = null
|
|
47
|
+
|
|
48
|
+
before(async function() {
|
|
49
|
+
w = wo(getOpt())
|
|
50
|
+
await w.createStorage()
|
|
51
|
+
await w.delAll()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('insert 3筆, 空主鍵自動產生', async function() {
|
|
55
|
+
let msg = await w.insert(genRs())
|
|
56
|
+
assert.deepStrictEqual(msg, { n: 3, nInserted: 3, ok: 1 })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('save({autoInsert:true}) 修改2筆並插入1筆', async function() {
|
|
60
|
+
let msg = await w.save(genRsm(), { autoInsert: true })
|
|
61
|
+
assert.deepStrictEqual(msg, [
|
|
62
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
63
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
64
|
+
{ n: 1, nInserted: 1, nModified: 0, ok: 1 },
|
|
65
|
+
])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('select() 查全部得4筆且不含資料庫內部欄位', async function() {
|
|
69
|
+
let ss = sortByName(await w.select())
|
|
70
|
+
assert.strictEqual(ss.length, 4)
|
|
71
|
+
let [kettle, kettleM, peter, rosemary] = ss
|
|
72
|
+
assert.deepStrictEqual(Object.keys(peter).sort(), ['id', 'name', 'value'])
|
|
73
|
+
assert.ok(kettle.id.length > 0)
|
|
74
|
+
assert.strictEqual(kettle.name, 'kettle')
|
|
75
|
+
assert.strictEqual(kettle.value, 456)
|
|
76
|
+
assert.ok(kettleM.id.length > 0)
|
|
77
|
+
assert.strictEqual(kettleM.name, 'kettle(modify)')
|
|
78
|
+
assert.strictEqual(kettleM.value, null)
|
|
79
|
+
assert.deepStrictEqual(peter, { id: 'id-peter', name: 'peter(modify)', value: 123 })
|
|
80
|
+
assert.deepStrictEqual(rosemary, { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('select({主鍵}) 依主鍵查詢', async function() {
|
|
84
|
+
let so = await w.select({ id: 'id-rosemary' })
|
|
85
|
+
assert.deepStrictEqual(so, [{ id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 }])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('selectByPk 命中, 內容與select({主鍵})[0]相同', async function() {
|
|
89
|
+
let v = await w.selectByPk('id-rosemary')
|
|
90
|
+
let so = await w.select({ id: 'id-rosemary' })
|
|
91
|
+
assert.deepStrictEqual(v, so[0])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('selectByPk 未命中回null', async function() {
|
|
95
|
+
assert.strictEqual(await w.selectByPk('id-nothere'), null)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('selectByPk 主鍵值無效回null且不reject', async function() {
|
|
99
|
+
assert.strictEqual(await w.selectByPk(null), null)
|
|
100
|
+
assert.strictEqual(await w.selectByPk(undefined), null)
|
|
101
|
+
assert.strictEqual(await w.selectByPk(''), null)
|
|
102
|
+
assert.strictEqual(await w.selectByPk({}), null)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('select $and+$gt+$lt', async function() {
|
|
106
|
+
let sp = await w.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
|
|
107
|
+
assert.deepStrictEqual(sp, [{ id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 }])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('select $or+$gte+$lte', async function() {
|
|
111
|
+
let sp = await w.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
|
|
112
|
+
assert.strictEqual(sp.length, 1)
|
|
113
|
+
assert.strictEqual(sp[0].name, 'kettle')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('select 巢狀$or+$and+$ne+$in+$nin', async function() {
|
|
117
|
+
let sp = sortByName(await w.select({ '$or': [{ '$and': [{ value: { '$ne': 123 } }, { value: { '$in': [123, 321, 123.456, 456] } }, { value: { '$nin': [456, 654] } }] }, { '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 400 } }] }] }))
|
|
118
|
+
assert.strictEqual(sp.length, 2)
|
|
119
|
+
assert.strictEqual(sp[0].name, 'kettle')
|
|
120
|
+
assert.deepStrictEqual(sp[1], { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 })
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('select $regex不分大小寫', async function() {
|
|
124
|
+
let sr = await w.select({ name: { $regex: 'PeT', $options: '$i' } })
|
|
125
|
+
assert.deepStrictEqual(sr, [{ id: 'id-peter', name: 'peter(modify)', value: 123 }])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('del 刪除3筆並保留kettle', async function() {
|
|
129
|
+
let ss = await w.select()
|
|
130
|
+
let d = ss.filter((v) => v.name !== 'kettle')
|
|
131
|
+
let msg = await w.del(d)
|
|
132
|
+
assert.deepStrictEqual(msg, [
|
|
133
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
134
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
135
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
136
|
+
])
|
|
137
|
+
let left = await w.select()
|
|
138
|
+
assert.strictEqual(left.length, 1)
|
|
139
|
+
assert.strictEqual(left[0].name, 'kettle')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('delAll 清空後select回傳空陣列', async function() {
|
|
143
|
+
let msg = await w.delAll()
|
|
144
|
+
assert.deepStrictEqual(msg, { n: 1, nDeleted: 1, ok: 1 })
|
|
145
|
+
assert.deepStrictEqual(await w.select(), [])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
describe(`${label} spec`, function() {
|
|
152
|
+
|
|
153
|
+
let w = null
|
|
154
|
+
|
|
155
|
+
before(async function() {
|
|
156
|
+
w = wo(getOpt())
|
|
157
|
+
await w.createStorage()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
beforeEach(async function() {
|
|
161
|
+
await w.delAll()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('insert 主鍵已存在者跳過且不覆寫, 不reject', async function() {
|
|
165
|
+
await w.insert({ id: 'k1', name: 'first', value: 1 })
|
|
166
|
+
let msg = await w.insert({ id: 'k1', name: 'second', value: 2 })
|
|
167
|
+
assert.deepStrictEqual(msg, { n: 1, nInserted: 0, ok: 1 })
|
|
168
|
+
assert.deepStrictEqual(await w.selectByPk('k1'), { id: 'k1', name: 'first', value: 1 })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('insert 同批含重複主鍵僅首筆計入nInserted', async function() {
|
|
172
|
+
let msg = await w.insert([
|
|
173
|
+
{ id: 'k1', name: 'a' },
|
|
174
|
+
{ id: 'k1', name: 'b' },
|
|
175
|
+
{ id: 'k2', name: 'c' },
|
|
176
|
+
])
|
|
177
|
+
assert.deepStrictEqual(msg, { n: 3, nInserted: 2, ok: 1 })
|
|
178
|
+
assert.strictEqual((await w.selectByPk('k1')).name, 'a')
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('insert 全數已存在時nInserted為0且屬正常結果', async function() {
|
|
182
|
+
await w.insert([{ id: 'k1' }, { id: 'k2' }])
|
|
183
|
+
let msg = await w.insert([{ id: 'k1' }, { id: 'k2' }])
|
|
184
|
+
assert.deepStrictEqual(msg, { n: 2, nInserted: 0, ok: 1 })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('insert 輸入無效回空結果', async function() {
|
|
188
|
+
for (let v of [null, undefined, '', 0, [], {}]) {
|
|
189
|
+
assert.deepStrictEqual(await w.insert(v), { n: 0, nInserted: 0, ok: 1 })
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('save 合併後內容相同者不寫入, nModified為0', async function() {
|
|
194
|
+
await w.insert({ id: 'k1', name: 'A', value: 1 })
|
|
195
|
+
let msg = await w.save({ id: 'k1', name: 'A', value: 1 })
|
|
196
|
+
assert.deepStrictEqual(msg, [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }])
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('save 只給部份欄位且值與現值相同, nModified為0', async function() {
|
|
200
|
+
await w.insert({ id: 'k1', name: 'A', value: 1 })
|
|
201
|
+
let msg = await w.save({ id: 'k1', name: 'A' })
|
|
202
|
+
assert.deepStrictEqual(msg, [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }])
|
|
203
|
+
assert.deepStrictEqual(await w.selectByPk('k1'), { id: 'k1', name: 'A', value: 1 })
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('save 只給部份欄位而值不同, 未給欄位保留', async function() {
|
|
207
|
+
await w.insert({ id: 'k1', name: 'A', value: 1 })
|
|
208
|
+
let msg = await w.save({ id: 'k1', name: 'B' })
|
|
209
|
+
assert.deepStrictEqual(msg, [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }])
|
|
210
|
+
assert.deepStrictEqual(await w.selectByPk('k1'), { id: 'k1', name: 'B', value: 1 })
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('save 主鍵不存在且autoInsert為true則插入', async function() {
|
|
214
|
+
let msg = await w.save({ id: 'k9', name: 'N' }, { autoInsert: true })
|
|
215
|
+
assert.deepStrictEqual(msg, [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }])
|
|
216
|
+
assert.strictEqual((await w.selectByPk('k9')).name, 'N')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('save 主鍵不存在且autoInsert為false則不寫入', async function() {
|
|
220
|
+
let msg = await w.save({ id: 'k9', name: 'N' }, { autoInsert: false })
|
|
221
|
+
assert.deepStrictEqual(msg, [{ n: 0, nInserted: 0, nModified: 0, ok: 1 }])
|
|
222
|
+
assert.strictEqual(await w.selectByPk('k9'), null)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('save 單一物件亦回傳長度1之陣列', async function() {
|
|
226
|
+
let msg = await w.save({ id: 'k1', name: 'A' })
|
|
227
|
+
assert.ok(Array.isArray(msg))
|
|
228
|
+
assert.strictEqual(msg.length, 1)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('save 單筆失敗不中斷整批, 該筆ok為0並附err', async function() {
|
|
232
|
+
//以字串欄位塞入物件觸發sequelize於送出SQL前之驗證錯誤, 此驗證於JS層完成故各dialect一致
|
|
233
|
+
let msg = await w.save([
|
|
234
|
+
{ id: 'k1', name: 'ok1' },
|
|
235
|
+
{ id: 'k2', name: {} },
|
|
236
|
+
{ id: 'k3', name: 'ok3' },
|
|
237
|
+
])
|
|
238
|
+
assert.strictEqual(msg.length, 3)
|
|
239
|
+
assert.deepStrictEqual(msg[0], { n: 1, nInserted: 1, nModified: 0, ok: 1 })
|
|
240
|
+
assert.strictEqual(msg[1].ok, 0)
|
|
241
|
+
assert.strictEqual(typeof msg[1].err, 'string')
|
|
242
|
+
assert.ok(msg[1].err.length > 0)
|
|
243
|
+
assert.deepStrictEqual(msg[2], { n: 1, nInserted: 1, nModified: 0, ok: 1 })
|
|
244
|
+
//其餘筆數照常寫入
|
|
245
|
+
assert.strictEqual((await w.selectByPk('k1')).name, 'ok1')
|
|
246
|
+
assert.strictEqual((await w.selectByPk('k3')).name, 'ok3')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('save 輸入無效回空陣列', async function() {
|
|
250
|
+
for (let v of [null, undefined, '', 0, [], {}]) {
|
|
251
|
+
assert.deepStrictEqual(await w.save(v), [])
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('del 主鍵未命中為正常結果ok為1', async function() {
|
|
256
|
+
let msg = await w.del({ id: 'nothere' })
|
|
257
|
+
assert.deepStrictEqual(msg, [{ n: 0, nDeleted: 0, ok: 1 }])
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('del 未帶有效主鍵者ok為0並附err, 且與未命中可區辨', async function() {
|
|
261
|
+
let msg = await w.del({ name: 'no-pk' })
|
|
262
|
+
assert.strictEqual(msg.length, 1)
|
|
263
|
+
assert.strictEqual(msg[0].n, 0)
|
|
264
|
+
assert.strictEqual(msg[0].nDeleted, 0)
|
|
265
|
+
assert.strictEqual(msg[0].ok, 0)
|
|
266
|
+
assert.strictEqual(typeof msg[0].err, 'string')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('del 未帶有效主鍵不中斷整批', async function() {
|
|
270
|
+
await w.insert([{ id: 'k1' }, { id: 'k2' }])
|
|
271
|
+
let msg = await w.del([{ id: 'k1' }, { name: 'no-pk' }, { id: 'k2' }])
|
|
272
|
+
assert.strictEqual(msg.length, 3)
|
|
273
|
+
assert.deepStrictEqual(msg[0], { n: 1, nDeleted: 1, ok: 1 })
|
|
274
|
+
assert.strictEqual(msg[1].ok, 0)
|
|
275
|
+
assert.deepStrictEqual(msg[2], { n: 1, nDeleted: 1, ok: 1 })
|
|
276
|
+
assert.deepStrictEqual(await w.select(), [])
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('del 輸入無效回空陣列', async function() {
|
|
280
|
+
for (let v of [null, undefined, '', 0, [], {}]) {
|
|
281
|
+
assert.deepStrictEqual(await w.del(v), [])
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('delAll 帶條件且僅部份命中', async function() {
|
|
286
|
+
await w.insert([
|
|
287
|
+
{ id: 'k1', value: 1 },
|
|
288
|
+
{ id: 'k2', value: 2 },
|
|
289
|
+
{ id: 'k3', value: 2 },
|
|
290
|
+
])
|
|
291
|
+
let msg = await w.delAll({ value: 2 })
|
|
292
|
+
assert.deepStrictEqual(msg, { n: 2, nDeleted: 2, ok: 1 })
|
|
293
|
+
let ss = await w.select()
|
|
294
|
+
assert.strictEqual(ss.length, 1)
|
|
295
|
+
assert.strictEqual(ss[0].id, 'k1')
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('delAll 條件無命中回0且不視為錯誤', async function() {
|
|
299
|
+
let msg = await w.delAll({ id: 'nothere' })
|
|
300
|
+
assert.deepStrictEqual(msg, { n: 0, nDeleted: 0, ok: 1 })
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('select 無符合恆回空陣列', async function() {
|
|
304
|
+
assert.deepStrictEqual(await w.select({ id: 'nothere' }), [])
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
describe(`${label} insertBulk`, function() {
|
|
311
|
+
|
|
312
|
+
let w = null
|
|
313
|
+
|
|
314
|
+
before(async function() {
|
|
315
|
+
w = wo(getOpt())
|
|
316
|
+
await w.createStorage()
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
beforeEach(async function() {
|
|
320
|
+
await w.delAll()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('無衝突時nInserted等於n, 鍵集合與insert相同', async function() {
|
|
324
|
+
let msg = await w.insertBulk([{ id: 'k1', name: 'a' }, { id: 'k2', name: 'b' }])
|
|
325
|
+
assert.deepStrictEqual(msg, { n: 2, nInserted: 2, ok: 1 })
|
|
326
|
+
assert.strictEqual((await w.select()).length, 2)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('單一物件亦可, 回傳單一物件', async function() {
|
|
330
|
+
let msg = await w.insertBulk({ id: 'k1', name: 'a' })
|
|
331
|
+
assert.deepStrictEqual(msg, { n: 1, nInserted: 1, ok: 1 })
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('撞既有主鍵時整批reject, 且資料表無任何新增', async function() {
|
|
335
|
+
await w.insert({ id: 'k1', name: 'pre' })
|
|
336
|
+
let n0 = (await w.select()).length
|
|
337
|
+
let r = await w.insertBulk([{ id: 'k2' }, { id: 'k3' }, { id: 'k1' }])
|
|
338
|
+
.then(() => null)
|
|
339
|
+
.catch((err) => err)
|
|
340
|
+
assert.ok(r !== null, '未reject')
|
|
341
|
+
let ss = await w.select()
|
|
342
|
+
assert.strictEqual(ss.length, n0, '失敗後不得有任何新增')
|
|
343
|
+
assert.strictEqual(ss[0].name, 'pre', '既有數據不得被改動')
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('同批含重複主鍵時整批reject, 且資料表無任何新增', async function() {
|
|
347
|
+
let r = await w.insertBulk([{ id: 'k1' }, { id: 'k2' }, { id: 'k1' }])
|
|
348
|
+
.then(() => null)
|
|
349
|
+
.catch((err) => err)
|
|
350
|
+
assert.ok(r !== null, '未reject')
|
|
351
|
+
assert.deepStrictEqual(await w.select(), [])
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('大批次末筆衝突亦不得留下部份寫入', async function() {
|
|
355
|
+
//mssql因綁定參數上限會由驅動層拆為多語句, 未包交易時前段會落盤
|
|
356
|
+
this.timeout(120000)
|
|
357
|
+
await w.insert({ id: 'b-999', name: 'pre' })
|
|
358
|
+
let rs = []
|
|
359
|
+
for (let i = 0; i < 1000; i++) {
|
|
360
|
+
rs.push({ id: `b-${i}`, name: `n${i}`, value: i })
|
|
361
|
+
}
|
|
362
|
+
let r = await w.insertBulk(rs)
|
|
363
|
+
.then(() => null)
|
|
364
|
+
.catch((err) => err)
|
|
365
|
+
assert.ok(r !== null, '未reject')
|
|
366
|
+
let ss = await w.select()
|
|
367
|
+
assert.strictEqual(ss.length, 1, `失敗後不得有任何新增, 實得${ss.length}筆`)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('輸入無效回空結果', async function() {
|
|
371
|
+
for (let v of [null, undefined, '', 0, [], {}]) {
|
|
372
|
+
assert.deepStrictEqual(await w.insertBulk(v), { n: 0, nInserted: 0, ok: 1 })
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('autoGenPk為true時未帶主鍵者自動產生', async function() {
|
|
377
|
+
let msg = await w.insertBulk([{ name: 'a' }, { name: 'b' }])
|
|
378
|
+
assert.deepStrictEqual(msg, { n: 2, nInserted: 2, ok: 1 })
|
|
379
|
+
let ss = await w.select()
|
|
380
|
+
assert.strictEqual(ss.length, 2)
|
|
381
|
+
assert.ok(ss[0].id.length > 0)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('autoGenPk為false時未帶主鍵須reject且同批皆不寫入', async function() {
|
|
385
|
+
let wNoGen = wo(getOpt({ autoGenPk: false }))
|
|
386
|
+
let r = await wNoGen.insertBulk([{ id: 'ok-1', name: 'a' }, { name: 'no-pk' }])
|
|
387
|
+
.then(() => null)
|
|
388
|
+
.catch((err) => err)
|
|
389
|
+
assert.ok(r !== null, '未reject')
|
|
390
|
+
assert.ok(String(r.message || r).indexOf('autoGenPk is false') >= 0)
|
|
391
|
+
assert.strictEqual(await w.selectByPk('ok-1'), null)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('成功發出change事件, 失敗發出error事件, mode皆為insertBulk', async function() {
|
|
395
|
+
let evs = []
|
|
396
|
+
let fnC = (mode, data, res) => evs.push({ ev: 'change', mode, res })
|
|
397
|
+
let fnE = (mode, data, err) => evs.push({ ev: 'error', mode, err })
|
|
398
|
+
w.on('change', fnC)
|
|
399
|
+
w.on('error', fnE)
|
|
400
|
+
|
|
401
|
+
await w.insertBulk({ id: 'k1' })
|
|
402
|
+
await w.insertBulk({ id: 'k1' }).catch(() => {})
|
|
403
|
+
|
|
404
|
+
w.off('change', fnC)
|
|
405
|
+
w.off('error', fnE)
|
|
406
|
+
|
|
407
|
+
assert.strictEqual(evs.length, 2)
|
|
408
|
+
assert.deepStrictEqual(evs[0], { ev: 'change', mode: 'insertBulk', res: { n: 1, nInserted: 1, ok: 1 } })
|
|
409
|
+
assert.strictEqual(evs[1].ev, 'error')
|
|
410
|
+
assert.strictEqual(evs[1].mode, 'insertBulk')
|
|
411
|
+
assert.strictEqual(typeof evs[1].err, 'string')
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('無衝突時與insert之可觀察結果完全相同', async function() {
|
|
415
|
+
let rs = [{ id: 'k1', name: 'a' }, { id: 'k2', name: 'b' }]
|
|
416
|
+
let m1 = await w.insertBulk(rs)
|
|
417
|
+
await w.delAll()
|
|
418
|
+
let m2 = await w.insert(rs)
|
|
419
|
+
assert.deepStrictEqual(m1, m2)
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('呼叫端已給transaction時, 失敗僅回滾本次而不影響交易內先前之寫入', async function() {
|
|
423
|
+
let instance = await w.init()
|
|
424
|
+
let transaction = await w.genTransaction()
|
|
425
|
+
let connState = { instance, transaction }
|
|
426
|
+
try {
|
|
427
|
+
|
|
428
|
+
//交易內先寫入一筆
|
|
429
|
+
await w.insert({ id: 'tx-1', name: 'A' }, connState)
|
|
430
|
+
|
|
431
|
+
//本次insertBulk撞該筆而失敗
|
|
432
|
+
let r = await w.insertBulk([{ id: 'tx-2' }, { id: 'tx-1' }], connState)
|
|
433
|
+
.then(() => null)
|
|
434
|
+
.catch((err) => err)
|
|
435
|
+
assert.ok(r !== null, '未reject')
|
|
436
|
+
|
|
437
|
+
//交易內先前之寫入須保留, 本次之tx-2須不存在
|
|
438
|
+
let ssIn = await w.select(null, connState)
|
|
439
|
+
let ids = ssIn.map((v) => v.id).sort()
|
|
440
|
+
assert.deepStrictEqual(ids, ['tx-1'])
|
|
441
|
+
|
|
442
|
+
await transaction.commit()
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
await transaction.rollback().catch(() => {})
|
|
446
|
+
throw err
|
|
447
|
+
}
|
|
448
|
+
finally {
|
|
449
|
+
await instance.close().catch(() => {})
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
//commit後應僅有tx-1
|
|
453
|
+
let ss = await w.select()
|
|
454
|
+
assert.deepStrictEqual(ss.map((v) => v.id), ['tx-1'])
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
describe(`${label} autoGenPk`, function() {
|
|
461
|
+
|
|
462
|
+
let w = null
|
|
463
|
+
let wNoGen = null
|
|
464
|
+
|
|
465
|
+
before(async function() {
|
|
466
|
+
w = wo(getOpt())
|
|
467
|
+
wNoGen = wo(getOpt({ autoGenPk: false }))
|
|
468
|
+
await w.createStorage()
|
|
469
|
+
await w.delAll()
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('autoGenPk為true時未帶主鍵者自動產生', async function() {
|
|
473
|
+
let msg = await w.insert({ name: 'auto-1' })
|
|
474
|
+
assert.deepStrictEqual(msg, { n: 1, nInserted: 1, ok: 1 })
|
|
475
|
+
let ss = await w.select({ name: 'auto-1' })
|
|
476
|
+
assert.strictEqual(ss.length, 1)
|
|
477
|
+
assert.ok(ss[0].id.length > 0)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('autoGenPk為true時save未帶主鍵者亦自動產生', async function() {
|
|
481
|
+
let msg = await w.save({ name: 'auto-2' })
|
|
482
|
+
assert.deepStrictEqual(msg, [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }])
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('autoGenPk為false時insert未帶主鍵須reject', async function() {
|
|
486
|
+
let r = await wNoGen.insert({ name: 'no-pk' })
|
|
487
|
+
.then(() => null)
|
|
488
|
+
.catch((err) => err)
|
|
489
|
+
assert.ok(r !== null, 'insert未reject')
|
|
490
|
+
assert.ok(String(r.message || r).indexOf('autoGenPk is false') >= 0)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it('autoGenPk為false時save未帶主鍵須reject', async function() {
|
|
494
|
+
let r = await wNoGen.save({ name: 'no-pk' })
|
|
495
|
+
.then(() => null)
|
|
496
|
+
.catch((err) => err)
|
|
497
|
+
assert.ok(r !== null, 'save未reject')
|
|
498
|
+
assert.ok(String(r.message || r).indexOf('autoGenPk is false') >= 0)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('autoGenPk為false而reject時, 同批之有效筆數亦不得被寫入', async function() {
|
|
502
|
+
await w.delAll()
|
|
503
|
+
let r = await wNoGen.insert([{ id: 'ok-1', name: 'a' }, { name: 'no-pk' }])
|
|
504
|
+
.then(() => null)
|
|
505
|
+
.catch((err) => err)
|
|
506
|
+
assert.ok(r !== null)
|
|
507
|
+
assert.strictEqual(await w.selectByPk('ok-1'), null)
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('autoGenPk為false時del不受影響, 未帶主鍵仍為該筆ok為0', async function() {
|
|
511
|
+
let msg = await wNoGen.del({ name: 'no-pk' })
|
|
512
|
+
assert.strictEqual(msg.length, 1)
|
|
513
|
+
assert.strictEqual(msg[0].ok, 0)
|
|
514
|
+
assert.strictEqual(typeof msg[0].err, 'string')
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('autoGenPk為true但主鍵欄位非字串類型時, 補值須以明確訊息reject', async function() {
|
|
518
|
+
let wBad = wo(getOpt({ pk: 'value' }))
|
|
519
|
+
let r = await wBad.insert({ name: 'bad-pk' })
|
|
520
|
+
.then(() => null)
|
|
521
|
+
.catch((err) => err)
|
|
522
|
+
assert.ok(r !== null, '未reject')
|
|
523
|
+
let msg = String(r.message || r)
|
|
524
|
+
assert.ok(msg.indexOf('autoGenPk is true') >= 0)
|
|
525
|
+
assert.ok(msg.indexOf('FLOAT') >= 0)
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
describe(`${label} event`, function() {
|
|
532
|
+
|
|
533
|
+
let w = null
|
|
534
|
+
|
|
535
|
+
before(async function() {
|
|
536
|
+
w = wo(getOpt())
|
|
537
|
+
await w.createStorage()
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
beforeEach(async function() {
|
|
541
|
+
await w.delAll()
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it('change事件參數為(mode, data, res)且逐筆函數整批發出一次', async function() {
|
|
545
|
+
let evs = []
|
|
546
|
+
let fn = (mode, data, res) => evs.push({ mode, data, res })
|
|
547
|
+
w.on('change', fn)
|
|
548
|
+
|
|
549
|
+
await w.insert([{ id: 'k1' }, { id: 'k2' }])
|
|
550
|
+
await w.del([{ id: 'k1' }, { id: 'k2' }])
|
|
551
|
+
|
|
552
|
+
w.off('change', fn)
|
|
553
|
+
|
|
554
|
+
let ms = evs.map((v) => v.mode)
|
|
555
|
+
assert.deepStrictEqual(ms, ['insert', 'del'])
|
|
556
|
+
assert.strictEqual(evs[0].data.length, 2)
|
|
557
|
+
assert.deepStrictEqual(evs[0].res, { n: 2, nInserted: 2, ok: 1 })
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
it('error事件參數為(mode, data, err)且err為字串', async function() {
|
|
561
|
+
let evs = []
|
|
562
|
+
let fn = (mode, data, err) => evs.push({ mode, data, err })
|
|
563
|
+
w.on('error', fn)
|
|
564
|
+
|
|
565
|
+
await w.del({ name: 'no-pk' })
|
|
566
|
+
|
|
567
|
+
w.off('error', fn)
|
|
568
|
+
|
|
569
|
+
assert.strictEqual(evs.length, 1)
|
|
570
|
+
assert.strictEqual(evs[0].mode, 'del')
|
|
571
|
+
assert.strictEqual(typeof evs[0].err, 'string')
|
|
572
|
+
assert.ok(evs[0].err.length > 0)
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it('正常結果不得發出error事件', async function() {
|
|
576
|
+
let evs = []
|
|
577
|
+
let fn = (mode, data, err) => evs.push({ mode, err })
|
|
578
|
+
w.on('error', fn)
|
|
579
|
+
|
|
580
|
+
await w.insert({ id: 'k1', name: 'A', value: 1 })
|
|
581
|
+
await w.insert({ id: 'k1', name: 'B' }) //已存在而跳過
|
|
582
|
+
await w.save({ id: 'k1', name: 'A', value: 1 }) //合併後內容相同
|
|
583
|
+
await w.del({ id: 'nothere' }) //主鍵未命中
|
|
584
|
+
await w.delAll({ id: 'nothere' }) //條件無命中
|
|
585
|
+
await w.selectByPk('nothere') //查無數據
|
|
586
|
+
await w.select({ id: 'nothere' })
|
|
587
|
+
|
|
588
|
+
w.off('error', fn)
|
|
589
|
+
|
|
590
|
+
assert.deepStrictEqual(evs, [])
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
it('訂閱函數拋錯不得影響本次操作之結果', async function() {
|
|
594
|
+
let fn = () => {
|
|
595
|
+
throw new Error('listener boom')
|
|
596
|
+
}
|
|
597
|
+
w.on('change', fn)
|
|
598
|
+
|
|
599
|
+
let msg = await w.insert({ id: 'k1' })
|
|
600
|
+
|
|
601
|
+
w.off('change', fn)
|
|
602
|
+
|
|
603
|
+
assert.deepStrictEqual(msg, { n: 1, nInserted: 1, ok: 1 })
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
it('操作行為不因有無註冊error監聽而改變', async function() {
|
|
607
|
+
let wWith = wo(getOpt())
|
|
608
|
+
let wWithout = wo(getOpt())
|
|
609
|
+
wWith.on('error', () => {})
|
|
610
|
+
|
|
611
|
+
//逐筆失敗
|
|
612
|
+
let a1 = await wWith.del({ name: 'no-pk' })
|
|
613
|
+
let b1 = await wWithout.del({ name: 'no-pk' })
|
|
614
|
+
assert.deepStrictEqual(a1, b1)
|
|
615
|
+
|
|
616
|
+
//整批性錯誤
|
|
617
|
+
let wgWith = wo(getOpt({ autoGenPk: false }))
|
|
618
|
+
let wgWithout = wo(getOpt({ autoGenPk: false }))
|
|
619
|
+
wgWith.on('error', () => {})
|
|
620
|
+
let a2 = await wgWith.insert({ name: 'no-pk' }).then(() => 'resolve').catch((err) => `reject:${err.message}`)
|
|
621
|
+
let b2 = await wgWithout.insert({ name: 'no-pk' }).then(() => 'resolve').catch((err) => `reject:${err.message}`)
|
|
622
|
+
assert.strictEqual(a2, b2)
|
|
623
|
+
assert.ok(a2.indexOf('reject:') === 0)
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
describe(`${label} transaction commit`, function() {
|
|
630
|
+
|
|
631
|
+
let w = null
|
|
632
|
+
let instance = null
|
|
633
|
+
|
|
634
|
+
before(async function() {
|
|
635
|
+
w = wo(getOpt())
|
|
636
|
+
await w.createStorage()
|
|
637
|
+
await w.delAll()
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
after(async function() {
|
|
641
|
+
if (instance) {
|
|
642
|
+
await instance.close().catch(() => {})
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
it('交易內insert+save+del, commit後數據持久化', async function() {
|
|
647
|
+
instance = await w.init()
|
|
648
|
+
let transaction = await w.genTransaction()
|
|
649
|
+
let connState = { instance, transaction }
|
|
650
|
+
|
|
651
|
+
let mi = await w.insert(genRs(), connState)
|
|
652
|
+
assert.deepStrictEqual(mi, { n: 3, nInserted: 3, ok: 1 })
|
|
653
|
+
|
|
654
|
+
let msv = await w.save(genRsm(), { ...connState, autoInsert: false })
|
|
655
|
+
assert.deepStrictEqual(msv, [
|
|
656
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
657
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
658
|
+
{ n: 0, nInserted: 0, nModified: 0, ok: 1 },
|
|
659
|
+
])
|
|
660
|
+
|
|
661
|
+
let md = await w.del({ id: 'id-rosemary' }, connState)
|
|
662
|
+
assert.deepStrictEqual(md, [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
663
|
+
|
|
664
|
+
//交易內select可查到暫時有效的數據
|
|
665
|
+
let ssIn = sortByName(await w.select(null, connState))
|
|
666
|
+
assert.strictEqual(ssIn.length, 2)
|
|
667
|
+
assert.strictEqual(ssIn[0].name, 'kettle')
|
|
668
|
+
assert.strictEqual(ssIn[1].name, 'peter(modify)')
|
|
669
|
+
|
|
670
|
+
await transaction.commit()
|
|
671
|
+
await instance.close()
|
|
672
|
+
instance = null
|
|
673
|
+
|
|
674
|
+
let ssFinal = sortByName(await w.select())
|
|
675
|
+
assert.strictEqual(ssFinal.length, 2)
|
|
676
|
+
assert.strictEqual(ssFinal[0].name, 'kettle')
|
|
677
|
+
assert.strictEqual(ssFinal[0].value, 456)
|
|
678
|
+
assert.deepStrictEqual(ssFinal[1], { id: 'id-peter', name: 'peter(modify)', value: 123 })
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
describe(`${label} transaction rollback`, function() {
|
|
685
|
+
|
|
686
|
+
let w = null
|
|
687
|
+
let instance = null
|
|
688
|
+
|
|
689
|
+
before(async function() {
|
|
690
|
+
w = wo(getOpt())
|
|
691
|
+
await w.createStorage()
|
|
692
|
+
await w.delAll()
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
after(async function() {
|
|
696
|
+
if (instance) {
|
|
697
|
+
await instance.close().catch(() => {})
|
|
698
|
+
}
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
it('交易內insert+save+del, rollback後數據全數還原', async function() {
|
|
702
|
+
instance = await w.init()
|
|
703
|
+
let transaction = await w.genTransaction()
|
|
704
|
+
let connState = { instance, transaction }
|
|
705
|
+
|
|
706
|
+
let mi = await w.insert(genRs(), connState)
|
|
707
|
+
assert.deepStrictEqual(mi, { n: 3, nInserted: 3, ok: 1 })
|
|
708
|
+
|
|
709
|
+
let msv = await w.save(genRsm(), { ...connState, autoInsert: false })
|
|
710
|
+
assert.deepStrictEqual(msv, [
|
|
711
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
712
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
713
|
+
{ n: 0, nInserted: 0, nModified: 0, ok: 1 },
|
|
714
|
+
])
|
|
715
|
+
|
|
716
|
+
let md = await w.del({ id: 'id-rosemary' }, connState)
|
|
717
|
+
assert.deepStrictEqual(md, [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
718
|
+
|
|
719
|
+
await transaction.rollback()
|
|
720
|
+
await instance.close()
|
|
721
|
+
instance = null
|
|
722
|
+
|
|
723
|
+
assert.deepStrictEqual(await w.select(), [])
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
}
|