w-orm-lowdb 1.0.13 → 1.0.14

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.
@@ -0,0 +1,53 @@
1
+ import WOrm from '../../src/WOrmLowdb.mjs'
2
+
3
+
4
+ //供unit-concurrency.test.mjs以子行程方式使用, 對同一資料庫檔案進行寫入
5
+ //註: 本檔置於test/lib之下, 令mocha之預設抓取範圍(test之第一層)不會將其誤認為測試檔
6
+
7
+ //argv
8
+ let url = process.argv[2]
9
+ let cl = process.argv[3]
10
+ let prefix = process.argv[4]
11
+ let count = parseInt(process.argv[5], 10)
12
+ let startAt = parseInt(process.argv[6], 10)
13
+ let mode = process.argv[7] //'insert' or 'save'
14
+
15
+
16
+ async function main() {
17
+
18
+ //wo
19
+ let wo = WOrm({ url, db: 'worm', cl })
20
+
21
+ //wait, 各行程同時起跑, 令寫入確實重疊
22
+ let ms = startAt - Date.now()
23
+ if (ms > 0) {
24
+ await new Promise((resolve) => {
25
+ setTimeout(resolve, ms)
26
+ })
27
+ }
28
+
29
+ //run
30
+ let nInserted = 0
31
+ let nOk = 0
32
+ for (let i = 0; i < count; i++) {
33
+ if (mode === 'save') {
34
+ let r = await wo.save({ id: 'shared', [`${prefix}${i}`]: i })
35
+ if (r[0].ok === 1) {
36
+ nOk++
37
+ }
38
+ }
39
+ else {
40
+ let r = await wo.insert({ id: `${prefix}${i}` })
41
+ nInserted += r.nInserted
42
+ }
43
+ }
44
+
45
+ //output
46
+ console.log(JSON.stringify({ prefix, nInserted, nOk }))
47
+
48
+ }
49
+ main()
50
+ .catch(function(err) {
51
+ console.log(JSON.stringify({ prefix, err: String(err) }))
52
+ process.exit(1)
53
+ })
@@ -0,0 +1,205 @@
1
+ import assert from 'assert'
2
+ import _ from 'lodash-es'
3
+ import w from 'wsemi'
4
+ import WOrm from '../src/WOrmLowdb.mjs'
5
+
6
+
7
+ describe('autoGenPk', function() {
8
+ let rt = null
9
+ let vans = {}
10
+ let vget = {}
11
+
12
+ before(async function() {
13
+
14
+ w.fsDeleteFile('./db_autogenpk.json')
15
+
16
+ let url = './db_autogenpk.json'
17
+
18
+ //autoGenPk預設為true
19
+ let woT = WOrm({ url, db: 'worm', cl: 'def' })
20
+
21
+ //insert未帶主鍵者由套件補值
22
+ vget[1] = await woT.insert([{ name: 'a' }, { name: 'b' }])
23
+ let rs = await woT.select()
24
+ vget[2] = _.size(_.filter(rs, function(v) {
25
+ return w.isestr(v.id)
26
+ }))
27
+
28
+ //自動產生之主鍵值須具足夠唯一性
29
+ vget[3] = _.size(_.uniq(_.map(rs, 'id')))
30
+
31
+ //save未帶主鍵者由套件補值後插入
32
+ vget[4] = await woT.save({ name: 'c' })
33
+
34
+ //insertBulk未帶主鍵者由套件補值
35
+ vget[5] = await woT.insertBulk([{ name: 'd' }, { name: 'e' }])
36
+ vget[6] = _.size(_.uniq(_.map(await woT.select(), 'id')))
37
+
38
+ //autoGenPk為false
39
+ let woF = WOrm({ url, db: 'worm', cl: 'nogen', autoGenPk: false })
40
+
41
+ //帶有效主鍵者正常寫入
42
+ vget[7] = await woF.insert([{ id: 'f1' }, { id: 'f2' }])
43
+
44
+ //insert未帶有效主鍵者以reject拋出整批性錯誤, 且同批之有效筆數亦不得被寫入
45
+ rt = null
46
+ await woF.insert([{ id: 'f3' }, { name: 'no-id' }])
47
+ .then(function(msg) {
48
+ rt = 'resolve: ' + JSON.stringify(msg)
49
+ })
50
+ .catch(function() {
51
+ rt = 'reject'
52
+ })
53
+ vget[8] = rt
54
+ vget[9] = await woF.selectByPk('f3')
55
+
56
+ //insertBulk未帶有效主鍵者亦以reject拋出
57
+ rt = null
58
+ await woF.insertBulk([{ id: 'f4' }, { name: 'no-id' }])
59
+ .then(function(msg) {
60
+ rt = 'resolve: ' + JSON.stringify(msg)
61
+ })
62
+ .catch(function() {
63
+ rt = 'reject'
64
+ })
65
+ vget[10] = rt
66
+ vget[11] = await woF.selectByPk('f4')
67
+
68
+ //save未帶有效主鍵者亦以reject拋出, 不得降為逐筆ok為0
69
+ rt = null
70
+ await woF.save([{ id: 'f1', v: 1 }, { name: 'no-id' }])
71
+ .then(function(msg) {
72
+ rt = 'resolve: ' + JSON.stringify(msg)
73
+ })
74
+ .catch(function() {
75
+ rt = 'reject'
76
+ })
77
+ vget[12] = rt
78
+ vget[13] = await woF.selectByPk('f1')
79
+
80
+ //del不受autoGenPk影響, 未帶有效主鍵者為該筆ok為0而非整批reject
81
+ rt = null
82
+ await woF.del([{ id: 'f2' }, { name: 'no-id' }])
83
+ .then(function(msg) {
84
+ rt = _.map(msg, 'ok')
85
+ })
86
+ .catch(function() {
87
+ rt = 'reject'
88
+ })
89
+ vget[14] = rt
90
+
91
+ //autoGenPk不得於各寫入函數之option逐次覆寫
92
+ rt = null
93
+ await woF.insert([{ name: 'no-id' }], { autoGenPk: true })
94
+ .then(function(msg) {
95
+ rt = 'resolve: ' + JSON.stringify(msg)
96
+ })
97
+ .catch(function() {
98
+ rt = 'reject'
99
+ })
100
+ vget[15] = rt
101
+ rt = null
102
+ await woT.insert([{ name: 'g' }], { autoGenPk: false })
103
+ .then(function(msg) {
104
+ rt = msg
105
+ })
106
+ .catch(function() {
107
+ rt = 'reject'
108
+ })
109
+ vget[16] = rt
110
+
111
+ //全表筆數確認, 僅f1留存(f2已被del刪除, 其餘批次皆整批reject而未寫入)
112
+ vget[17] = _.map(await woF.select(), 'id')
113
+
114
+ })
115
+
116
+ after(async function() {
117
+ w.fsDeleteFile('./db_autogenpk.json')
118
+ })
119
+
120
+ vans[1] = { n: 2, nInserted: 2, ok: 1 }
121
+ it(`should get ${JSON.stringify(vans[1])} for insert without pk when autoGenPk=true`, async function() {
122
+ assert.strict.deepStrictEqual(vget[1], vans[1])
123
+ })
124
+
125
+ vans[2] = 2
126
+ it(`should get ${JSON.stringify(vans[2])} for count of rows with generated pk`, async function() {
127
+ assert.strict.deepStrictEqual(vget[2], vans[2])
128
+ })
129
+
130
+ vans[3] = 2
131
+ it(`should get ${JSON.stringify(vans[3])} for count of unique generated pk`, async function() {
132
+ assert.strict.deepStrictEqual(vget[3], vans[3])
133
+ })
134
+
135
+ vans[4] = [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }]
136
+ it(`should get ${JSON.stringify(vans[4])} for save without pk when autoGenPk=true`, async function() {
137
+ assert.strict.deepStrictEqual(vget[4], vans[4])
138
+ })
139
+
140
+ vans[5] = { n: 2, nInserted: 2, ok: 1 }
141
+ it(`should get ${JSON.stringify(vans[5])} for insertBulk without pk when autoGenPk=true`, async function() {
142
+ assert.strict.deepStrictEqual(vget[5], vans[5])
143
+ })
144
+
145
+ vans[6] = 5
146
+ it(`should get ${JSON.stringify(vans[6])} for count of unique pk in table`, async function() {
147
+ assert.strict.deepStrictEqual(vget[6], vans[6])
148
+ })
149
+
150
+ vans[7] = { n: 2, nInserted: 2, ok: 1 }
151
+ it(`should get ${JSON.stringify(vans[7])} for insert with pk when autoGenPk=false`, async function() {
152
+ assert.strict.deepStrictEqual(vget[7], vans[7])
153
+ })
154
+
155
+ vans[8] = 'reject'
156
+ it(`should get ${JSON.stringify(vans[8])} for insert without pk when autoGenPk=false`, async function() {
157
+ assert.strict.deepStrictEqual(vget[8], vans[8])
158
+ })
159
+
160
+ vans[9] = null
161
+ it(`should get ${JSON.stringify(vans[9])} for valid row in the rejected batch of insert`, async function() {
162
+ assert.strict.deepStrictEqual(vget[9], vans[9])
163
+ })
164
+
165
+ vans[10] = 'reject'
166
+ it(`should get ${JSON.stringify(vans[10])} for insertBulk without pk when autoGenPk=false`, async function() {
167
+ assert.strict.deepStrictEqual(vget[10], vans[10])
168
+ })
169
+
170
+ vans[11] = null
171
+ it(`should get ${JSON.stringify(vans[11])} for valid row in the rejected batch of insertBulk`, async function() {
172
+ assert.strict.deepStrictEqual(vget[11], vans[11])
173
+ })
174
+
175
+ vans[12] = 'reject'
176
+ it(`should get ${JSON.stringify(vans[12])} for save without pk when autoGenPk=false`, async function() {
177
+ assert.strict.deepStrictEqual(vget[12], vans[12])
178
+ })
179
+
180
+ vans[13] = { id: 'f1' }
181
+ it(`should get ${JSON.stringify(vans[13])} for valid row in the rejected batch of save`, async function() {
182
+ assert.strict.deepStrictEqual(vget[13], vans[13])
183
+ })
184
+
185
+ vans[14] = [1, 0]
186
+ it(`should get ${JSON.stringify(vans[14])} for del which is not affected by autoGenPk`, async function() {
187
+ assert.strict.deepStrictEqual(vget[14], vans[14])
188
+ })
189
+
190
+ vans[15] = 'reject'
191
+ it(`should get ${JSON.stringify(vans[15])} for insert with option.autoGenPk=true when constructor is false`, async function() {
192
+ assert.strict.deepStrictEqual(vget[15], vans[15])
193
+ })
194
+
195
+ vans[16] = { n: 1, nInserted: 1, ok: 1 }
196
+ it(`should get ${JSON.stringify(vans[16])} for insert with option.autoGenPk=false when constructor is true`, async function() {
197
+ assert.strict.deepStrictEqual(vget[16], vans[16])
198
+ })
199
+
200
+ vans[17] = ['f1']
201
+ it(`should get ${JSON.stringify(vans[17])} for remaining ids when autoGenPk=false`, async function() {
202
+ assert.strict.deepStrictEqual(vget[17], vans[17])
203
+ })
204
+
205
+ })
@@ -77,8 +77,7 @@ describe('basic', function() {
77
77
  .then(function(msg) {
78
78
  // console.log('delAll then', msg)
79
79
  //考慮可能有不同初始測試數據
80
- // delAll then { n: 2, nDeleted: 2, ok: 1 }
81
- // delAll then { n: 3, nDeleted: 2, ok: 1 }
80
+ // delAll then { n: 0, nDeleted: 0, ok: 1 }
82
81
  //僅針對nDeleted與ok欄位比對
83
82
  rt = {
84
83
  nDeleted: msg.nDeleted,
@@ -107,19 +106,21 @@ describe('basic', function() {
107
106
  vget[2] = rt
108
107
 
109
108
  //save
109
+ //註: 第三筆之id為空字串, 於autoGenPk為true時會補上新主鍵值, 故必為未命中,
110
+ //而autoInsert為false故不插入, 該筆n為0
110
111
  rt = null
111
112
  // vans[3] = [
112
- // { n: 1, nModified: 1, ok: 1 },
113
- // { n: 1, nModified: 1, ok: 1 },
114
- // { n: 0, nModified: 0, ok: 1 }
113
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
114
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
115
+ // { n: 0, nInserted: 0, nModified: 0, ok: 1 }
115
116
  // ]
116
117
  await wo.save(rsm, { autoInsert: false })
117
118
  .then(function(msg) {
118
119
  // console.log('save then', msg)
119
120
  // save then [
120
- // { n: 1, nModified: 1, ok: 1 },
121
- // { n: 1, nModified: 1, ok: 1 },
122
- // { n: 0, nModified: 0, ok: 1 }
121
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
122
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
123
+ // { n: 0, nInserted: 0, nModified: 0, ok: 1 }
123
124
  // ]
124
125
  rt = msg
125
126
  })
@@ -135,7 +136,7 @@ describe('basic', function() {
135
136
  // { id: 'id-peter', name: 'peter(modify)', value: 123 },
136
137
  // { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
137
138
  // {
138
- // // id: {random id},
139
+ // // id: {auto gen id},
139
140
  // name: 'kettle',
140
141
  // value: 456
141
142
  // }
@@ -143,15 +144,6 @@ describe('basic', function() {
143
144
  await wo.select()
144
145
  .then(function(msg) {
145
146
  // console.log('select all then', msg)
146
- // select all [
147
- // { id: 'id-peter', name: 'peter(modify)', value: 123 },
148
- // { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
149
- // {
150
- // id: {random id},
151
- // name: 'kettle',
152
- // value: 456
153
- // }
154
- // ]
155
147
  rt = [
156
148
  msg[0],
157
149
  msg[1],
@@ -173,7 +165,6 @@ describe('basic', function() {
173
165
  await wo.select({ id: 'id-rosemary' })
174
166
  .then(function(msg) {
175
167
  // console.log('select then', msg)
176
- // select [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
177
168
  rt = msg
178
169
  })
179
170
  .catch(function(msg) {
@@ -188,7 +179,6 @@ describe('basic', function() {
188
179
  await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
189
180
  .then(function(msg) {
190
181
  // console.log('select by $and, $gt, $lt then', msg)
191
- // select by $and, $gt, $lt [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
192
182
  rt = msg
193
183
  })
194
184
  .catch(function(msg) {
@@ -201,7 +191,7 @@ describe('basic', function() {
201
191
  rt = null
202
192
  // vans[7] = [
203
193
  // {
204
- // // id: {random id},
194
+ // // id: {auto gen id},
205
195
  // name: 'kettle',
206
196
  // value: 456
207
197
  // }
@@ -209,13 +199,6 @@ describe('basic', function() {
209
199
  await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
210
200
  .then(function(msg) {
211
201
  // console.log('select by $or, $gte, $lte then', msg)
212
- // select by $or, $gte, $lte [
213
- // {
214
- // id: {random id},
215
- // name: 'kettle',
216
- // value: 456
217
- // }
218
- // ]
219
202
  rt = [
220
203
  {
221
204
  name: msg[0].name,
@@ -238,7 +221,7 @@ describe('basic', function() {
238
221
  // value: 123.456
239
222
  // },
240
223
  // {
241
- // // id: {random id},
224
+ // // id: {auto gen id},
242
225
  // name: 'kettle',
243
226
  // value: 456
244
227
  // }
@@ -246,18 +229,6 @@ describe('basic', function() {
246
229
  await wo.select({ '$or': [{ '$and': [{ value: { '$ne': 123 } }, { value: { '$in': [123, 321, 123.456, 456] } }, { value: { '$nin': [456, 654] } }] }, { '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 400 } }] }] })
247
230
  .then(function(msg) {
248
231
  // console.log('select by $or, $and, $ne, $in, $nin then', msg)
249
- // select by $or, $and, $ne, $in, $nin [
250
- // {
251
- // id: 'id-rosemary',
252
- // name: 'rosemary(modify)',
253
- // value: 123.456
254
- // },
255
- // {
256
- // id: {random id},
257
- // name: 'kettle',
258
- // value: 456
259
- // }
260
- // ]
261
232
  rt = [
262
233
  msg[0],
263
234
  {
@@ -272,28 +243,44 @@ describe('basic', function() {
272
243
  })
273
244
  vget[8] = rt
274
245
 
275
- // //select by regex
246
+ // //select by regex //mingo不支援regex
276
247
  // rt = null
277
248
  // vans[9] = [{ id: 'id-peter', name: 'peter(modify)', value: 123 }]
278
249
  // await wo.select({ name: { $regex: 'PeT', $options: '$i' } })
279
250
  // .then(function(msg) {
280
- // // console.log('select by regex then', msg)
281
- // // selectReg [ { id: 'id-peter', name: 'peter(modify)', value: 123 } ]
282
251
  // rt = msg
283
252
  // })
284
253
  // .catch(function(msg) {
285
- // // console.log('select by regex catch', msg)
286
254
  // rt = msg.toString()
287
255
  // })
288
256
  // vget[9] = rt
289
257
 
258
+ //select回傳恆為陣列, 無符合數據回空陣列
259
+ rt = null
260
+ // vans[12] = []
261
+ await wo.select({ id: 'id-not-existed' })
262
+ .then(function(msg) {
263
+ rt = msg
264
+ })
265
+ .catch(function(msg) {
266
+ rt = msg.toString()
267
+ })
268
+ vget[12] = rt
269
+
270
+ //selectByPk, 命中者內容須與select({主鍵})[0]相同
271
+ rt = null
272
+ // vans[13] = true
273
+ let vp = await wo.selectByPk('id-peter')
274
+ let vs = await wo.select({ id: 'id-peter' })
275
+ vget[13] = JSON.stringify(vp) === JSON.stringify(vs[0])
276
+
290
277
  //save
291
278
  rt = null
292
- // vans[10] = [ { n: 1, nModified: 1, ok: 1 } ]
279
+ // vans[10] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
293
280
  await wo.save(rsa, { autoInsert: true })
294
281
  .then(function(msg) {
295
282
  // console.log('save then', msg)
296
- // save then [ { n: 1, nModified: 1, ok: 1 } ]
283
+ // save then [ { n: 1, nInserted: 0, nModified: 1, ok: 1 } ]
297
284
  rt = msg
298
285
  })
299
286
  .catch(function(msg) {
@@ -341,9 +328,9 @@ describe('basic', function() {
341
328
  })
342
329
 
343
330
  vans[3] = [
344
- { n: 1, nModified: 1, ok: 1 },
345
- { n: 1, nModified: 1, ok: 1 },
346
- { n: 0, nModified: 0, ok: 1 }
331
+ { n: 1, nInserted: 0, nModified: 1, ok: 1 },
332
+ { n: 1, nInserted: 0, nModified: 1, ok: 1 },
333
+ { n: 0, nInserted: 0, nModified: 0, ok: 1 }
347
334
  ]
348
335
  it(`should get ${JSON.stringify(vans[3])} for save(autoInsert=false)`, async function() {
349
336
  assert.strict.deepStrictEqual(vget[3], vans[3])
@@ -353,7 +340,7 @@ describe('basic', function() {
353
340
  { id: 'id-peter', name: 'peter(modify)', value: 123 },
354
341
  { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
355
342
  {
356
- // id: {random id},
343
+ // id: {auto gen id},
357
344
  name: 'kettle',
358
345
  value: 456
359
346
  }
@@ -374,7 +361,7 @@ describe('basic', function() {
374
361
 
375
362
  vans[7] = [
376
363
  {
377
- // id: {random id},
364
+ // id: {auto gen id},
378
365
  name: 'kettle',
379
366
  value: 456
380
367
  }
@@ -390,7 +377,7 @@ describe('basic', function() {
390
377
  value: 123.456
391
378
  },
392
379
  {
393
- // id: {random id},
380
+ // id: {auto gen id},
394
381
  name: 'kettle',
395
382
  value: 456
396
383
  }
@@ -404,7 +391,17 @@ describe('basic', function() {
404
391
  // assert.strict.deepStrictEqual(vget[9], vans[9])
405
392
  // })
406
393
 
407
- vans[10] = [{ n: 1, nModified: 1, ok: 1 }]
394
+ vans[12] = []
395
+ it(`should get ${JSON.stringify(vans[12])} for select without matched data`, async function() {
396
+ assert.strict.deepStrictEqual(vget[12], vans[12])
397
+ })
398
+
399
+ vans[13] = true
400
+ it(`should get ${JSON.stringify(vans[13])} for selectByPk equal to select({pk})[0]`, async function() {
401
+ assert.strict.deepStrictEqual(vget[13], vans[13])
402
+ })
403
+
404
+ vans[10] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
408
405
  it(`should get ${JSON.stringify(vans[10])} for save(autoInsert=true)`, async function() {
409
406
  assert.strict.deepStrictEqual(vget[10], vans[10])
410
407
  })
@@ -100,7 +100,7 @@ describe('cache', function() {
100
100
 
101
101
  //save (更新rosemary, 觸發快取重設)
102
102
  rt = null
103
- // vans[3] = [{ n: 1, nModified: 1, ok: 1 }]
103
+ // vans[3] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
104
104
  await wo.save(rsm, { autoInsert: false })
105
105
  .then(function(msg) {
106
106
  // console.log('save then', msg)
@@ -155,7 +155,7 @@ describe('cache', function() {
155
155
  assert.strict.deepStrictEqual(vget[2], vans[2])
156
156
  })
157
157
 
158
- vans[3] = [{ n: 1, nModified: 1, ok: 1 }]
158
+ vans[3] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
159
159
  it(`should get ${JSON.stringify(vans[3])} for save (invalidate cache)`, async function() {
160
160
  assert.strict.deepStrictEqual(vget[3], vans[3])
161
161
  })
@@ -0,0 +1,140 @@
1
+ import assert from 'assert'
2
+ import { spawn } from 'child_process'
3
+ import _ from 'lodash-es'
4
+ import w from 'wsemi'
5
+ import WOrm from '../src/WOrmLowdb.mjs'
6
+
7
+
8
+ describe('concurrency', function() {
9
+ let vans = {}
10
+ let vget = {}
11
+
12
+ before(async function() {
13
+
14
+ //runProc, 以子行程執行寫入, 回傳其stdout之JSON
15
+ let runProc = function(args) {
16
+ return new Promise(function(resolve) {
17
+ let p = spawn(process.execPath, ['./test/lib/procConcurrency.mjs', ...args], { cwd: process.cwd() })
18
+ let out = ''
19
+ p.stdout.on('data', function(d) {
20
+ out += d
21
+ })
22
+ p.stderr.on('data', function(d) {
23
+ out += d
24
+ })
25
+ p.on('close', function() {
26
+ let r = null
27
+ try {
28
+ r = JSON.parse(_.trim(out))
29
+ }
30
+ catch (err) {
31
+ r = { err: _.trim(out) }
32
+ }
33
+ resolve(r)
34
+ })
35
+ })
36
+ }
37
+
38
+ //單一行程內併發: 30個並行insert不同主鍵, nInserted總和與資料表筆數皆須為30
39
+ w.fsDeleteFile('./db_concurrency_a.json')
40
+ let woA = WOrm({ url: './db_concurrency_a.json', db: 'worm', cl: 'users' })
41
+ let rsA = await Promise.all(_.map(_.range(30), function(i) {
42
+ return woA.insert({ id: `k${i}` })
43
+ }))
44
+ vget[1] = [_.sumBy(rsA, 'nInserted'), _.size(await woA.select())]
45
+
46
+ //單一行程內併發: 30個並行insert於同一主鍵, 僅一次得以插入
47
+ w.fsDeleteFile('./db_concurrency_b.json')
48
+ let woB = WOrm({ url: './db_concurrency_b.json', db: 'worm', cl: 'users' })
49
+ let rsB = await Promise.all(_.map(_.range(30), function() {
50
+ return woB.insert({ id: 'same' })
51
+ }))
52
+ vget[2] = [_.sumBy(rsB, 'nInserted'), _.size(await woB.select())]
53
+
54
+ //單一行程內併發: 20個並行save於同一列各寫入不同欄位, 全部欄位須保留(不遺失更新)
55
+ w.fsDeleteFile('./db_concurrency_c.json')
56
+ let woC = WOrm({ url: './db_concurrency_c.json', db: 'worm', cl: 'users' })
57
+ await woC.insert({ id: 's' })
58
+ let rsC = await Promise.all(_.map(_.range(20), function(i) {
59
+ return woC.save({ id: 's', [`f${i}`]: i })
60
+ }))
61
+ let vC = await woC.selectByPk('s')
62
+ vget[3] = [
63
+ _.size(_.filter(_.flatten(rsC), { ok: 1 })),
64
+ _.size(_.filter(_.keys(vC), function(k) {
65
+ return _.startsWith(k, 'f')
66
+ })),
67
+ ]
68
+
69
+ //單一行程內併發: 同一檔案之多個實例共用同一佇列, 併發寫入亦不遺失
70
+ w.fsDeleteFile('./db_concurrency_d.json')
71
+ let wos = _.map(_.range(5), function() {
72
+ return WOrm({ url: './db_concurrency_d.json', db: 'worm', cl: 'users' })
73
+ })
74
+ let rsD = await Promise.all(_.flatten(_.map(wos, function(wo, j) {
75
+ return _.map(_.range(10), function(i) {
76
+ return wo.insert({ id: `m${j}-${i}` })
77
+ })
78
+ })))
79
+ vget[4] = [_.sumBy(rsD, 'nInserted'), _.size(await wos[0].select())]
80
+
81
+ //跨行程併發: 依README之宣告為不成立, 此處以實測重現其失效
82
+ //失效有二: (一)兩行程各持舊快照整檔寫回而遺失對方之寫入,
83
+ //(二)steno之暫存檔名由資料庫檔名推導, 兩行程共用同一暫存檔而使後者之rename拋ENOENT
84
+ //註: 任一輪重現即足以證實其不成立, 故最多跑3輪
85
+ let nRound = 0
86
+ let broken = false
87
+ while (nRound < 3 && !broken) {
88
+ nRound++
89
+ let url = `./db_concurrency_x${nRound}.json`
90
+ w.fsDeleteFile(url)
91
+ let startAt = Date.now() + 1500
92
+ let [ra, rb] = await Promise.all([
93
+ runProc([url, 'users', 'A-', '200', `${startAt}`, 'insert']),
94
+ runProc([url, 'users', 'B-', '200', `${startAt}`, 'insert']),
95
+ ])
96
+ let wo = WOrm({ url, db: 'worm', cl: 'users' })
97
+ let nRow = _.size(await wo.select())
98
+ let hasErr = w.isestr(_.get(ra, 'err')) || w.isestr(_.get(rb, 'err'))
99
+ let nLost = 400 - nRow
100
+ broken = hasErr || nLost > 0
101
+ // console.log('cross process detail', { nRound, nRow, nLost, hasErr })
102
+ w.fsDeleteFile(url)
103
+ }
104
+ vget[5] = broken
105
+
106
+ })
107
+
108
+ after(async function() {
109
+ w.fsDeleteFile('./db_concurrency_a.json')
110
+ w.fsDeleteFile('./db_concurrency_b.json')
111
+ w.fsDeleteFile('./db_concurrency_c.json')
112
+ w.fsDeleteFile('./db_concurrency_d.json')
113
+ })
114
+
115
+ vans[1] = [30, 30]
116
+ it(`should get ${JSON.stringify(vans[1])} for 30 concurrent inserts with different pk in one process`, async function() {
117
+ assert.strict.deepStrictEqual(vget[1], vans[1])
118
+ })
119
+
120
+ vans[2] = [1, 1]
121
+ it(`should get ${JSON.stringify(vans[2])} for 30 concurrent inserts with same pk in one process`, async function() {
122
+ assert.strict.deepStrictEqual(vget[2], vans[2])
123
+ })
124
+
125
+ vans[3] = [20, 20]
126
+ it(`should get ${JSON.stringify(vans[3])} for 20 concurrent saves on same row in one process`, async function() {
127
+ assert.strict.deepStrictEqual(vget[3], vans[3])
128
+ })
129
+
130
+ vans[4] = [50, 50]
131
+ it(`should get ${JSON.stringify(vans[4])} for concurrent inserts by multiple instances on same file`, async function() {
132
+ assert.strict.deepStrictEqual(vget[4], vans[4])
133
+ })
134
+
135
+ vans[5] = true
136
+ it(`should get ${JSON.stringify(vans[5])} for cross process concurrency being broken as declared in README`, async function() {
137
+ assert.strict.deepStrictEqual(vget[5], vans[5])
138
+ })
139
+
140
+ })