w-orm-lowdb 1.0.13 → 1.0.15
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 +121 -15
- package/dist/w-orm-lowdb.umd.js +2 -2
- package/dist/w-orm-lowdb.umd.js.map +1 -1
- package/docs/WOrmLowdb.html +628 -27
- package/docs/WOrmLowdb.mjs.html +1068 -779
- package/docs/index.html +2 -2
- package/g-basic.mjs +203 -185
- package/package.json +3 -3
- package/src/WOrmLowdb.mjs +1066 -777
- package/test/lib/procConcurrency.mjs +53 -0
- package/test/unit-autogenpk.test.mjs +205 -0
- package/test/{basic.test.mjs → unit-basic.test.mjs} +51 -54
- package/test/{cache.test.mjs → unit-cache.test.mjs} +2 -2
- package/test/unit-concurrency.test.mjs +140 -0
- package/test/unit-del.test.mjs +140 -0
- package/test/unit-delall.test.mjs +153 -0
- package/test/unit-event.test.mjs +252 -0
- package/test/unit-insert.test.mjs +120 -0
- package/test/unit-insertbulk.test.mjs +188 -0
- package/test/unit-returnlist.test.mjs +159 -0
- package/test/unit-save.test.mjs +199 -0
- package/test/unit-selectbypk.test.mjs +121 -0
- package/toolg/gDistRollup.mjs +3 -3
|
@@ -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:
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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] = [
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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[
|
|
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
|
+
})
|