w-orm-lmdb 1.0.18 → 1.0.20
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 +44 -2
- package/dist/w-orm-lmdb.umd.js +2 -2
- package/dist/w-orm-lmdb.umd.js.map +1 -1
- package/docs/WOrmLmdb.html +303 -12
- package/docs/WOrmLmdb.mjs.html +184 -15
- package/docs/index.html +2 -2
- package/g-basic.mjs +43 -1
- package/package.json +1 -1
- package/src/WOrmLmdb.mjs +182 -13
- package/test/unit-closed.test.mjs +108 -0
- package/test/unit-insertbulk.test.mjs +205 -0
- package/test/unit-returnlist.test.mjs +195 -0
- package/toolg/gDistRollup.mjs +1 -1
- package//350/263/207/346/226/231/345/272/253/345/207/275/346/225/270/345/233/236/345/202/263/345/256/232/347/276/251/350/250/210/347/256/227.md +228 -42
- package/dist/req-mingo.umd.js +0 -7
- package/dist/req-mingo.umd.js.map +0 -1
package/src/WOrmLmdb.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { open } from 'lmdb'
|
|
1
|
+
import { open, ABORT } from 'lmdb'
|
|
2
2
|
// import mingo from 'mingo' //mingo內未更新import寫法, 會導致ERR_UNSUPPORTED_DIR_IMPORT, 故須改用require引入使用
|
|
3
3
|
import mingo from './reqMingo.js'
|
|
4
4
|
import size from 'lodash-es/size.js'
|
|
@@ -28,13 +28,13 @@ import waitFun from 'wsemi/src/waitFun.mjs'
|
|
|
28
28
|
* 故不監聽亦能取得完整資訊,且監聽與否不改變任何操作之回傳值。
|
|
29
29
|
*
|
|
30
30
|
* change事件,參數為(mode, data, res),於資料實際異動成功後發出:
|
|
31
|
-
* mode為操作別字串,可為'insert'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
|
|
31
|
+
* mode為操作別字串,可為'insert'、'insertBulk'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
|
|
32
32
|
* data為本次操作之輸入數據,delAll固定為null。
|
|
33
33
|
* res為本次操作之回傳結果。
|
|
34
34
|
* 逐筆函數以整批為單位發出一次而不逐筆發出,select與selectByPk不發出本事件。
|
|
35
35
|
*
|
|
36
36
|
* error事件,參數為(mode, data, err),於操作發生錯誤時發出:
|
|
37
|
-
* mode為操作別字串,可為'select'、'selectByPk'、'insert'、'save'、'del'、'delAll'。
|
|
37
|
+
* mode為操作別字串,可為'select'、'selectByPk'、'insert'、'insertBulk'、'save'、'del'、'delAll'。
|
|
38
38
|
* data為本次操作之輸入數據,無輸入數據者為null。
|
|
39
39
|
* err為錯誤訊息字串,內容與正規管道所送出者一致。
|
|
40
40
|
* 整批性錯誤於reject之前發出;逐筆失敗於該筆結果定案後發出,每筆一次。
|
|
@@ -126,13 +126,25 @@ function WOrmLmdb(opt = {}) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
//waitOpen, 等待client開啟, 因getRange與get於client關閉時會拋錯
|
|
129
|
+
//closed為終態: LMDB env一旦關閉不會自行回到open, 續等必然逾時, 故直接拋出而非空轉至逾時
|
|
130
|
+
//各函數入口已有procClosed守門, 此處之終態判斷為縱深防禦, 攔[操作進行中被close]之競態
|
|
131
|
+
//等待參數收斂: 開啟中為毫秒級過渡, 50次x200ms=10秒已極寬裕, 不使非終態之等待拖至waitFun預設之200秒
|
|
129
132
|
let waitOpen = async () => {
|
|
133
|
+
if (client.status === 'closed') {
|
|
134
|
+
throw new Error('client is closed, instance can not be used after close()')
|
|
135
|
+
}
|
|
130
136
|
await waitFun(() => {
|
|
131
|
-
if (client.status === 'closed') {
|
|
132
|
-
console.log(`client.status[${client.status}], level is closed`)
|
|
133
|
-
}
|
|
134
137
|
return client.status === 'open'
|
|
135
|
-
})
|
|
138
|
+
}, { attemptNum: 50, timeInterval: 200 })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//procClosed, 各函數入口之終態守門: close()後再操作屬整批性錯誤, 立即reject令誤用當場可見
|
|
142
|
+
//不可靜默回正常空值(如null或未命中), 否則[已關閉]與[查無資料]同形,
|
|
143
|
+
//去重類呼叫端將把每筆判為未存在而fail-open, 整批重送下游昂貴動作
|
|
144
|
+
let procClosed = (mode, data) => {
|
|
145
|
+
let msg = `can not ${mode} by closed client, instance can not be used after close()`
|
|
146
|
+
emitError(mode, data, msg)
|
|
147
|
+
return Promise.reject(new Error(msg))
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
//emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
|
|
@@ -212,6 +224,11 @@ function WOrmLmdb(opt = {}) {
|
|
|
212
224
|
* @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
|
|
213
225
|
*/
|
|
214
226
|
async function select(find = {}) {
|
|
227
|
+
|
|
228
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
229
|
+
if (client.status === 'closed') {
|
|
230
|
+
return procClosed('select', null)
|
|
231
|
+
}
|
|
215
232
|
let isErr = false
|
|
216
233
|
let res = null
|
|
217
234
|
|
|
@@ -271,6 +288,11 @@ function WOrmLmdb(opt = {}) {
|
|
|
271
288
|
* @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵則回傳null,reject回傳錯誤訊息
|
|
272
289
|
*/
|
|
273
290
|
async function selectByPk(pk) {
|
|
291
|
+
|
|
292
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
293
|
+
if (client.status === 'closed') {
|
|
294
|
+
return procClosed('selectByPk', null)
|
|
295
|
+
}
|
|
274
296
|
let isErr = false
|
|
275
297
|
let res = null
|
|
276
298
|
|
|
@@ -320,13 +342,30 @@ function WOrmLmdb(opt = {}) {
|
|
|
320
342
|
*
|
|
321
343
|
* @memberOf WOrmLmdb
|
|
322
344
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
323
|
-
* @
|
|
345
|
+
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
346
|
+
* @param {Boolean} [option.returnList=false] 輸入是否改回傳逐筆結果布林值,預設false。給true時回傳與輸入等長保序之陣列,各筆為{n,nInserted,ok},nInserted為1即該筆為新增;聚合計數只回答有幾筆是新的,逐筆結果方能回答是哪幾筆,供下游僅對新資料執行昂貴動作。回傳形式由呼叫點靜態決定,共用之結果處理程式碼不可跨不同取值之呼叫點混用
|
|
347
|
+
* @returns {Promise} 回傳Promise,resolve依returnList回傳插入結果:預設回傳聚合物件{n,nInserted,ok},n為輸入筆數、nInserted為實際插入筆數;returnList為true時回傳逐筆陣列,各筆n恆為1(主鍵命中或經插入)、ok恆為1(insert之錯誤皆屬整批性而reject,不進逐筆);reject回傳錯誤訊息
|
|
324
348
|
*/
|
|
325
|
-
async function insert(data) {
|
|
349
|
+
async function insert(data, option = {}) {
|
|
350
|
+
|
|
351
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
352
|
+
if (client.status === 'closed') {
|
|
353
|
+
return procClosed('insert', data)
|
|
354
|
+
}
|
|
326
355
|
let isErr = false
|
|
327
356
|
|
|
357
|
+
//returnList
|
|
358
|
+
let returnList = get(option, 'returnList')
|
|
359
|
+
if (!isbol(returnList)) {
|
|
360
|
+
returnList = false
|
|
361
|
+
}
|
|
362
|
+
|
|
328
363
|
//check
|
|
329
364
|
if (!iseobj(data) && !isearr(data)) {
|
|
365
|
+
if (returnList) {
|
|
366
|
+
//輸入無效, 對齊save與del之T5規定回傳空陣列
|
|
367
|
+
return []
|
|
368
|
+
}
|
|
330
369
|
return {
|
|
331
370
|
n: 0,
|
|
332
371
|
nInserted: 0,
|
|
@@ -375,9 +414,122 @@ function WOrmLmdb(opt = {}) {
|
|
|
375
414
|
})
|
|
376
415
|
|
|
377
416
|
//res
|
|
417
|
+
if (returnList) {
|
|
418
|
+
//逐筆結果, 與輸入等長保序, 元素沿用逐筆結果之家族形狀(同save與del之元素)
|
|
419
|
+
//n恆為1(主鍵命中既有或經插入而產生), ok恆為1(insert之錯誤皆屬整批性而reject, 不進逐筆),
|
|
420
|
+
//資訊由nInserted承載, filter(v=>v.nInserted===1)之長度必等於聚合模式之nInserted
|
|
421
|
+
res = map(ltdone, function(done) {
|
|
422
|
+
return {
|
|
423
|
+
n: 1,
|
|
424
|
+
nInserted: done ? 1 : 0,
|
|
425
|
+
ok: 1,
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
res = {
|
|
431
|
+
n: nAll,
|
|
432
|
+
nInserted: nPush,
|
|
433
|
+
ok: 1,
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
isErr = true
|
|
440
|
+
res = err
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
//update, 不能保證插入多少, 一律重設快取
|
|
444
|
+
_cache = null
|
|
445
|
+
|
|
446
|
+
//emit, 於change可能須使用select, 故須放在重設快取之後
|
|
447
|
+
if (!isErr) {
|
|
448
|
+
emitChange('insert', data, res)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
//check
|
|
452
|
+
if (isErr) {
|
|
453
|
+
|
|
454
|
+
//emit, 整批性錯誤須於reject之前發出
|
|
455
|
+
emitError('insert', data, res)
|
|
456
|
+
|
|
457
|
+
return Promise.reject(res)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return res
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 批次插入數據,全批視為一個單位,全部插入成功或一筆都不寫入
|
|
465
|
+
* 註: 本函數非insert之加速版,兩者衝突政策不同。insert於主鍵已存在時跳過該筆而整批ok為1,
|
|
466
|
+
* 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
|
|
467
|
+
* 註: 於本套件不會較insert快,因insert本即以Promise.all一次送出全部條件寫入而非逐筆await,
|
|
468
|
+
* 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
|
|
469
|
+
*
|
|
470
|
+
* @memberOf WOrmLmdb
|
|
471
|
+
* @param {Object|Array} data 輸入數據物件或陣列
|
|
472
|
+
* @returns {Promise} 回傳Promise,resolve回傳插入結果物件{n,nInserted,ok},n為輸入筆數、nInserted成功時恆等於n,任一筆主鍵已存在則reject回傳錯誤訊息
|
|
473
|
+
*/
|
|
474
|
+
async function insertBulk(data) {
|
|
475
|
+
|
|
476
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
477
|
+
if (client.status === 'closed') {
|
|
478
|
+
return procClosed('insertBulk', data)
|
|
479
|
+
}
|
|
480
|
+
let isErr = false
|
|
481
|
+
|
|
482
|
+
//check
|
|
483
|
+
if (!iseobj(data) && !isearr(data)) {
|
|
484
|
+
return {
|
|
485
|
+
n: 0,
|
|
486
|
+
nInserted: 0,
|
|
487
|
+
ok: 1,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//cloneDeep, 與外部數據脫勾
|
|
492
|
+
data = cloneDeep(data)
|
|
493
|
+
|
|
494
|
+
//res
|
|
495
|
+
let res = null
|
|
496
|
+
try {
|
|
497
|
+
|
|
498
|
+
//check
|
|
499
|
+
if (!isarr(data)) {
|
|
500
|
+
data = [data]
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
//check id
|
|
504
|
+
data = procPk(data, 'insertBulk')
|
|
505
|
+
|
|
506
|
+
//nAll
|
|
507
|
+
let nAll = size(data)
|
|
508
|
+
|
|
509
|
+
//childTransaction, 全有全無由交易回滾保證
|
|
510
|
+
//註: 非同步之client.transaction於中止時並不回滾, 實測其內之put仍會落盤, 故此處須用childTransaction
|
|
511
|
+
//註: 交易內之client.get讀得到同一交易稍早之put, 故同批含重複主鍵者亦會於此被偵測為衝突
|
|
512
|
+
//註: 存在與否採鍵層判定, 與insert之ifNoExists一致
|
|
513
|
+
let pkConflict = null
|
|
514
|
+
await client.childTransaction(() => {
|
|
515
|
+
for (let v of data) {
|
|
516
|
+
if (client.get(v.id) !== undefined) {
|
|
517
|
+
pkConflict = v.id
|
|
518
|
+
return ABORT
|
|
519
|
+
}
|
|
520
|
+
client.put(v.id, v)
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
//check, 衝突則整批視為失敗, 交易已回滾故無任何寫入
|
|
525
|
+
if (isestr(pkConflict)) {
|
|
526
|
+
throw new Error(`can not insertBulk by existed id[${pkConflict}]`)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
//res, 未衝突則全數插入, 故nInserted恆等於n
|
|
378
530
|
res = {
|
|
379
531
|
n: nAll,
|
|
380
|
-
nInserted:
|
|
532
|
+
nInserted: nAll,
|
|
381
533
|
ok: 1,
|
|
382
534
|
}
|
|
383
535
|
|
|
@@ -392,14 +544,14 @@ function WOrmLmdb(opt = {}) {
|
|
|
392
544
|
|
|
393
545
|
//emit, 於change可能須使用select, 故須放在重設快取之後
|
|
394
546
|
if (!isErr) {
|
|
395
|
-
emitChange('
|
|
547
|
+
emitChange('insertBulk', data, res)
|
|
396
548
|
}
|
|
397
549
|
|
|
398
550
|
//check
|
|
399
551
|
if (isErr) {
|
|
400
552
|
|
|
401
553
|
//emit, 整批性錯誤須於reject之前發出
|
|
402
|
-
emitError('
|
|
554
|
+
emitError('insertBulk', data, res)
|
|
403
555
|
|
|
404
556
|
return Promise.reject(res)
|
|
405
557
|
}
|
|
@@ -417,6 +569,11 @@ function WOrmLmdb(opt = {}) {
|
|
|
417
569
|
* @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列,各筆為{n,nInserted,nModified,ok},n為主鍵命中筆數,單筆失敗者ok為0並附err,reject回傳錯誤訊息
|
|
418
570
|
*/
|
|
419
571
|
async function save(data, option = {}) {
|
|
572
|
+
|
|
573
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
574
|
+
if (client.status === 'closed') {
|
|
575
|
+
return procClosed('save', data)
|
|
576
|
+
}
|
|
420
577
|
let isErr = false
|
|
421
578
|
|
|
422
579
|
//check
|
|
@@ -618,6 +775,11 @@ function WOrmLmdb(opt = {}) {
|
|
|
618
775
|
* @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,各筆為{n,nDeleted,ok},n為主鍵命中筆數,單筆失敗或未給有效id者ok為0並附err,reject回傳錯誤訊息
|
|
619
776
|
*/
|
|
620
777
|
async function del(data) {
|
|
778
|
+
|
|
779
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
780
|
+
if (client.status === 'closed') {
|
|
781
|
+
return procClosed('del', data)
|
|
782
|
+
}
|
|
621
783
|
let isErr = false
|
|
622
784
|
|
|
623
785
|
//check
|
|
@@ -749,6 +911,11 @@ function WOrmLmdb(opt = {}) {
|
|
|
749
911
|
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},n與nDeleted同為實際刪除筆數,reject回傳錯誤訊息
|
|
750
912
|
*/
|
|
751
913
|
async function delAll(find = {}) {
|
|
914
|
+
|
|
915
|
+
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
|
|
916
|
+
if (client.status === 'closed') {
|
|
917
|
+
return procClosed('delAll', null)
|
|
918
|
+
}
|
|
752
919
|
let isErr = false
|
|
753
920
|
|
|
754
921
|
//res
|
|
@@ -851,7 +1018,8 @@ function WOrmLmdb(opt = {}) {
|
|
|
851
1018
|
return res
|
|
852
1019
|
}
|
|
853
1020
|
|
|
854
|
-
//close, 關閉內部LMDB env並釋放檔案鎖, close後該實例即報廢, 須重新new WOrmLmdb
|
|
1021
|
+
//close, 關閉內部LMDB env並釋放檔案鎖, close後該實例即報廢, 須重新new WOrmLmdb,
|
|
1022
|
+
//再呼叫任何操作函數將立即reject(訊息明示closed), 不與[查無資料]同形
|
|
855
1023
|
let close = async () => {
|
|
856
1024
|
if (client && client.status !== 'closed') {
|
|
857
1025
|
await client.close() //lmdb root database之close(), 回傳Promise
|
|
@@ -862,6 +1030,7 @@ function WOrmLmdb(opt = {}) {
|
|
|
862
1030
|
ee.select = select
|
|
863
1031
|
ee.selectByPk = selectByPk
|
|
864
1032
|
ee.insert = insert
|
|
1033
|
+
ee.insertBulk = insertBulk
|
|
865
1034
|
ee.save = save
|
|
866
1035
|
ee.del = del
|
|
867
1036
|
ee.delAll = delAll
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import _ from 'lodash-es'
|
|
3
|
+
import w from 'wsemi'
|
|
4
|
+
import WOrm from '../src/WOrmLmdb.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
describe('closed', function() {
|
|
8
|
+
let vans = {}
|
|
9
|
+
let vget = {}
|
|
10
|
+
|
|
11
|
+
before(async function() {
|
|
12
|
+
|
|
13
|
+
w.fsDeleteFolder('./_db_closed')
|
|
14
|
+
|
|
15
|
+
let wo = WOrm({ url: './_db_closed', db: 'worm', cl: 'users' })
|
|
16
|
+
await wo.insert({ id: 'k1', name: 'peter' })
|
|
17
|
+
await wo.close()
|
|
18
|
+
|
|
19
|
+
//evs, close後之操作屬整批性錯誤, 依T10.3須發出error事件
|
|
20
|
+
let evs = []
|
|
21
|
+
wo.on('error', function(mode, data, err) {
|
|
22
|
+
evs.push({ mode, isStr: _.isString(err) })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
//run, close後各函數皆須立即reject(而非空轉約200秒), 且訊息明示closed而不與[查無資料]同形
|
|
26
|
+
let run = async (fn) => {
|
|
27
|
+
let t0 = Date.now()
|
|
28
|
+
let r = await fn()
|
|
29
|
+
.then(function(msg) {
|
|
30
|
+
return { type: 'resolve', msg }
|
|
31
|
+
})
|
|
32
|
+
.catch(function(err) {
|
|
33
|
+
return { type: 'reject', closed: String(err).indexOf('closed') >= 0 }
|
|
34
|
+
})
|
|
35
|
+
r.fast = (Date.now() - t0) < 2000
|
|
36
|
+
return r
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
vget[1] = await run(() => wo.select())
|
|
40
|
+
vget[2] = await run(() => wo.selectByPk('k1'))
|
|
41
|
+
vget[3] = await run(() => wo.insert({ id: 'k2' }))
|
|
42
|
+
vget[4] = await run(() => wo.insert({ id: 'k2' }, { returnList: true }))
|
|
43
|
+
vget[5] = await run(() => wo.insertBulk({ id: 'k3' }))
|
|
44
|
+
vget[6] = await run(() => wo.save({ id: 'k1', name: 'mary' }))
|
|
45
|
+
vget[7] = await run(() => wo.del({ id: 'k1' }))
|
|
46
|
+
vget[8] = await run(() => wo.delAll())
|
|
47
|
+
vget[9] = _.map(evs, 'mode')
|
|
48
|
+
vget[10] = _.uniq(_.map(evs, 'isStr'))
|
|
49
|
+
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
let rj = { type: 'reject', closed: true, fast: true }
|
|
53
|
+
|
|
54
|
+
vans[1] = rj
|
|
55
|
+
it(`should get ${JSON.stringify(vans[1])} for select after close`, async function() {
|
|
56
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
//不可回null, 否則[已關閉]與[查無資料]同形而fail-open
|
|
60
|
+
vans[2] = rj
|
|
61
|
+
it(`should get ${JSON.stringify(vans[2])} for selectByPk after close`, async function() {
|
|
62
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
vans[3] = rj
|
|
66
|
+
it(`should get ${JSON.stringify(vans[3])} for insert after close`, async function() {
|
|
67
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
vans[4] = rj
|
|
71
|
+
it(`should get ${JSON.stringify(vans[4])} for insert(returnList) after close`, async function() {
|
|
72
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
//守門須在進入childTransaction之前, 否則已關閉env上之非同步回調會拋出未捕捉例外使行程崩潰
|
|
76
|
+
vans[5] = rj
|
|
77
|
+
it(`should get ${JSON.stringify(vans[5])} for insertBulk after close`, async function() {
|
|
78
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
//屬整批性錯誤而reject, 不可降為逐筆ok:0之resolve
|
|
82
|
+
vans[6] = rj
|
|
83
|
+
it(`should get ${JSON.stringify(vans[6])} for save after close`, async function() {
|
|
84
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
//不可回[{n:0,nDeleted:0,ok:1}]之正常未命中結果
|
|
88
|
+
vans[7] = rj
|
|
89
|
+
it(`should get ${JSON.stringify(vans[7])} for del after close`, async function() {
|
|
90
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
vans[8] = rj
|
|
94
|
+
it(`should get ${JSON.stringify(vans[8])} for delAll after close`, async function() {
|
|
95
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
vans[9] = ['select', 'selectByPk', 'insert', 'insert', 'insertBulk', 'save', 'del', 'delAll']
|
|
99
|
+
it(`should get ${JSON.stringify(vans[9])} for error events after close`, async function() {
|
|
100
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
vans[10] = [true]
|
|
104
|
+
it(`should get ${JSON.stringify(vans[10])} for err of error events being string`, async function() {
|
|
105
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
})
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import _ from 'lodash-es'
|
|
3
|
+
import w from 'wsemi'
|
|
4
|
+
import WOrm from '../src/WOrmLmdb.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
describe('insertBulk', function() {
|
|
8
|
+
let rt = null
|
|
9
|
+
let vans = {}
|
|
10
|
+
let vget = {}
|
|
11
|
+
|
|
12
|
+
before(async function() {
|
|
13
|
+
|
|
14
|
+
w.fsDeleteFolder('./_db_insertbulk')
|
|
15
|
+
|
|
16
|
+
let url = './_db_insertbulk'
|
|
17
|
+
|
|
18
|
+
let wo = WOrm({ url, db: 'worm', cl: 'users' })
|
|
19
|
+
|
|
20
|
+
//輸入無效
|
|
21
|
+
vget[1] = await wo.insertBulk(null)
|
|
22
|
+
|
|
23
|
+
//正常插入, nInserted須恆等於n
|
|
24
|
+
rt = null
|
|
25
|
+
// vans[2] = { n: 3, nInserted: 3, ok: 1 }
|
|
26
|
+
await wo.insertBulk([{ id: 'a' }, { id: 'b' }, { id: 'c' }])
|
|
27
|
+
.then(function(msg) {
|
|
28
|
+
// console.log('insertBulk then', msg)
|
|
29
|
+
// insertBulk then { n: 3, nInserted: 3, ok: 1 }
|
|
30
|
+
rt = msg
|
|
31
|
+
})
|
|
32
|
+
.catch(function(msg) {
|
|
33
|
+
// console.log('insertBulk catch', msg)
|
|
34
|
+
rt = 'reject: ' + msg.toString()
|
|
35
|
+
})
|
|
36
|
+
vget[2] = rt
|
|
37
|
+
vget[3] = _.map(_.sortBy(await wo.select(), 'id'), 'id')
|
|
38
|
+
|
|
39
|
+
//單一物件亦須回傳單一物件而非陣列
|
|
40
|
+
rt = null
|
|
41
|
+
// vans[4] = { n: 1, nInserted: 1, ok: 1 }
|
|
42
|
+
await wo.insertBulk({ id: 'd' })
|
|
43
|
+
.then(function(msg) {
|
|
44
|
+
rt = msg
|
|
45
|
+
})
|
|
46
|
+
.catch(function(msg) {
|
|
47
|
+
rt = 'reject: ' + msg.toString()
|
|
48
|
+
})
|
|
49
|
+
vget[4] = rt
|
|
50
|
+
|
|
51
|
+
//含既有主鍵須整批reject, 且不得寫入任何一筆
|
|
52
|
+
rt = null
|
|
53
|
+
await wo.insertBulk([{ id: 'e' }, { id: 'a' }, { id: 'f' }])
|
|
54
|
+
.then(function(msg) {
|
|
55
|
+
rt = 'resolve: ' + JSON.stringify(msg)
|
|
56
|
+
})
|
|
57
|
+
.catch(function() {
|
|
58
|
+
rt = 'reject'
|
|
59
|
+
})
|
|
60
|
+
vget[5] = rt
|
|
61
|
+
vget[6] = _.map(_.sortBy(await wo.select(), 'id'), 'id')
|
|
62
|
+
|
|
63
|
+
//同批含重複主鍵亦視為衝突
|
|
64
|
+
rt = null
|
|
65
|
+
await wo.insertBulk([{ id: 'g' }, { id: 'g' }])
|
|
66
|
+
.then(function(msg) {
|
|
67
|
+
rt = 'resolve: ' + JSON.stringify(msg)
|
|
68
|
+
})
|
|
69
|
+
.catch(function() {
|
|
70
|
+
rt = 'reject'
|
|
71
|
+
})
|
|
72
|
+
vget[7] = rt
|
|
73
|
+
vget[8] = _.map(_.sortBy(await wo.select(), 'id'), 'id')
|
|
74
|
+
|
|
75
|
+
//事件: 成功發change, 衝突發error且不發change
|
|
76
|
+
let evs = []
|
|
77
|
+
wo.on('change', function(mode) {
|
|
78
|
+
evs.push('change:' + mode)
|
|
79
|
+
})
|
|
80
|
+
wo.on('error', function(mode, data, err) {
|
|
81
|
+
evs.push({ ev: 'error:' + mode, isStr: _.isString(err) })
|
|
82
|
+
})
|
|
83
|
+
await wo.insertBulk({ id: 'h' })
|
|
84
|
+
vget[9] = _.map(evs, function(v) {
|
|
85
|
+
return v.ev || v
|
|
86
|
+
})
|
|
87
|
+
evs = []
|
|
88
|
+
await wo.insertBulk({ id: 'h' }).catch(function() {})
|
|
89
|
+
vget[10] = _.map(evs, function(v) {
|
|
90
|
+
return v.ev || v
|
|
91
|
+
})
|
|
92
|
+
vget[11] = _.get(evs, '0.isStr')
|
|
93
|
+
await wo.close()
|
|
94
|
+
|
|
95
|
+
//autoGenPk為true時補值
|
|
96
|
+
let woGen = WOrm({ url, db: 'worm', cl: 'gen' })
|
|
97
|
+
rt = null
|
|
98
|
+
// vans[12] = { n: 2, nInserted: 2, ok: 1 }
|
|
99
|
+
await woGen.insertBulk([{ v: 1 }, { v: 2 }])
|
|
100
|
+
.then(function(msg) {
|
|
101
|
+
rt = msg
|
|
102
|
+
})
|
|
103
|
+
.catch(function(msg) {
|
|
104
|
+
rt = 'reject: ' + msg.toString()
|
|
105
|
+
})
|
|
106
|
+
vget[12] = rt
|
|
107
|
+
vget[13] = _.size(_.filter(await woGen.select(), function(v) {
|
|
108
|
+
return w.isestr(v.id)
|
|
109
|
+
}))
|
|
110
|
+
await woGen.close()
|
|
111
|
+
|
|
112
|
+
//autoGenPk為false而未帶有效主鍵, 須reject且不得寫入任何一筆
|
|
113
|
+
let woNo = WOrm({ url, db: 'worm', cl: 'nogen', autoGenPk: false })
|
|
114
|
+
rt = null
|
|
115
|
+
await woNo.insertBulk([{ id: 'x1' }, { v: 1 }])
|
|
116
|
+
.then(function(msg) {
|
|
117
|
+
rt = 'resolve: ' + JSON.stringify(msg)
|
|
118
|
+
})
|
|
119
|
+
.catch(function() {
|
|
120
|
+
rt = 'reject'
|
|
121
|
+
})
|
|
122
|
+
vget[14] = rt
|
|
123
|
+
vget[15] = _.size(await woNo.select())
|
|
124
|
+
await woNo.close()
|
|
125
|
+
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
vans[1] = { n: 0, nInserted: 0, ok: 1 }
|
|
129
|
+
it(`should get ${JSON.stringify(vans[1])} for insertBulk with invalid input`, async function() {
|
|
130
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
vans[2] = { n: 3, nInserted: 3, ok: 1 }
|
|
134
|
+
it(`should get ${JSON.stringify(vans[2])} for insertBulk without conflict`, async function() {
|
|
135
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
vans[3] = ['a', 'b', 'c']
|
|
139
|
+
it(`should get ${JSON.stringify(vans[3])} for records after insertBulk`, async function() {
|
|
140
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
vans[4] = { n: 1, nInserted: 1, ok: 1 }
|
|
144
|
+
it(`should get ${JSON.stringify(vans[4])} for insertBulk by single object`, async function() {
|
|
145
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
//全有全無: 衝突須整批reject
|
|
149
|
+
vans[5] = 'reject'
|
|
150
|
+
it(`should get ${JSON.stringify(vans[5])} for insertBulk with existed id`, async function() {
|
|
151
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
//全有全無: 衝突時同批之無衝突筆數亦不得寫入
|
|
155
|
+
vans[6] = ['a', 'b', 'c', 'd']
|
|
156
|
+
it(`should get ${JSON.stringify(vans[6])} for no writing when insertBulk rejected`, async function() {
|
|
157
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
vans[7] = 'reject'
|
|
161
|
+
it(`should get ${JSON.stringify(vans[7])} for insertBulk with duplicated id in same batch`, async function() {
|
|
162
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
vans[8] = ['a', 'b', 'c', 'd']
|
|
166
|
+
it(`should get ${JSON.stringify(vans[8])} for no writing when duplicated id in same batch`, async function() {
|
|
167
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
vans[9] = ['change:insertBulk']
|
|
171
|
+
it(`should get ${JSON.stringify(vans[9])} for change event of insertBulk`, async function() {
|
|
172
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
vans[10] = ['error:insertBulk']
|
|
176
|
+
it(`should get ${JSON.stringify(vans[10])} for error event of insertBulk with conflict`, async function() {
|
|
177
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
vans[11] = true
|
|
181
|
+
it(`should get ${JSON.stringify(vans[11])} for err of error event being string`, async function() {
|
|
182
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
vans[12] = { n: 2, nInserted: 2, ok: 1 }
|
|
186
|
+
it(`should get ${JSON.stringify(vans[12])} for insertBulk without id by autoGenPk=true`, async function() {
|
|
187
|
+
assert.strict.deepStrictEqual(vget[12], vans[12])
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
vans[13] = 2
|
|
191
|
+
it(`should get ${JSON.stringify(vans[13])} for records with generated id`, async function() {
|
|
192
|
+
assert.strict.deepStrictEqual(vget[13], vans[13])
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
vans[14] = 'reject'
|
|
196
|
+
it(`should get ${JSON.stringify(vans[14])} for insertBulk without id by autoGenPk=false`, async function() {
|
|
197
|
+
assert.strict.deepStrictEqual(vget[14], vans[14])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
vans[15] = 0
|
|
201
|
+
it(`should get ${JSON.stringify(vans[15])} for no writing when autoGenPk=false rejected`, async function() {
|
|
202
|
+
assert.strict.deepStrictEqual(vget[15], vans[15])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
})
|