w-orm-mongodb 1.1.37 → 1.1.38
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 +471 -288
- package/dist/w-orm-mongodb.umd.js +2 -2
- package/dist/w-orm-mongodb.umd.js.map +1 -1
- package/docs/WOrmMongodb.html +214 -34
- package/docs/WOrmMongodb.mjs.html +1260 -829
- package/docs/index.html +2 -2
- package/g-basic.mjs +16 -6
- package/g-gfs.mjs +102 -102
- package/g-unique.mjs +66 -0
- package/package.json +2 -3
- package/script.txt +0 -1
- package/src/WOrmMongodb.mjs +1258 -827
- package/test/api-basic.test.mjs +1200 -0
- package/test/api-gfs.test.mjs +574 -0
- package/test/lib/api-setup.mjs +142 -0
- package/test/basic.test.mjs +0 -348
- package/test/gfs.test.mjs +0 -172
package/README.md
CHANGED
|
@@ -1,288 +1,471 @@
|
|
|
1
|
-
# w-orm-mongodb
|
|
2
|
-
An operator for mongodb in nodejs.
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
[](https://npmjs.org/package/w-orm-mongodb)
|
|
6
|
-
[](https://npmjs.org/package/w-orm-mongodb)
|
|
7
|
-
[](https://npmjs.org/package/w-orm-mongodb)
|
|
8
|
-
[](https://npmjs.org/package/w-orm-mongodb)
|
|
9
|
-
[](https://www.jsdelivr.com/package/npm/w-orm-mongodb)
|
|
10
|
-
|
|
11
|
-
## Documentation
|
|
12
|
-
To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-orm-mongodb/
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
### Using npm(ES6 module):
|
|
17
|
-
```alias
|
|
18
|
-
npm i w-orm-mongodb
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
#### Example for collection
|
|
22
|
-
> **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-basic.mjs)]
|
|
23
|
-
```alias
|
|
24
|
-
import WOrm from './src/WOrmMongodb.mjs'
|
|
25
|
-
//import WOrm from './dist/w-orm-mongodb.umd.js'
|
|
26
|
-
|
|
27
|
-
let opt = {
|
|
28
|
-
url: 'mongodb://username:password@127.0.0.1:27017',
|
|
29
|
-
db: 'worm',
|
|
30
|
-
cl: 'users',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let rs = [
|
|
34
|
-
{
|
|
35
|
-
id: 'id-peter',
|
|
36
|
-
name: 'peter',
|
|
37
|
-
value: 123,
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'id-rosemary',
|
|
41
|
-
name: 'rosemary',
|
|
42
|
-
value: 123.456,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: '',
|
|
46
|
-
name: 'kettle',
|
|
47
|
-
value: 456,
|
|
48
|
-
},
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
let rsm = [
|
|
52
|
-
{
|
|
53
|
-
id: 'id-peter',
|
|
54
|
-
name: 'peter(modify)'
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'id-rosemary',
|
|
58
|
-
name: 'rosemary(modify)'
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: '',
|
|
62
|
-
name: 'kettle(modify)'
|
|
63
|
-
},
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
async function test() {
|
|
67
|
-
|
|
68
|
-
//wo
|
|
69
|
-
let wo = WOrm(opt)
|
|
70
|
-
|
|
71
|
-
//on
|
|
72
|
-
wo.on('change', function(mode, data, res) {
|
|
73
|
-
console.log('change', mode)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
//delAll
|
|
77
|
-
await wo.delAll()
|
|
78
|
-
.then(function(msg) {
|
|
79
|
-
console.log('delAll then', msg)
|
|
80
|
-
})
|
|
81
|
-
.catch(function(msg) {
|
|
82
|
-
console.log('delAll catch', msg)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
//insert
|
|
86
|
-
await wo.insert(rs)
|
|
87
|
-
.then(function(msg) {
|
|
88
|
-
console.log('insert then', msg)
|
|
89
|
-
})
|
|
90
|
-
.catch(function(msg) {
|
|
91
|
-
console.log('insert catch', msg)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
//save
|
|
95
|
-
await wo.save(rsm, { autoInsert: false })
|
|
96
|
-
.then(function(msg) {
|
|
97
|
-
console.log('save then', msg)
|
|
98
|
-
})
|
|
99
|
-
.catch(function(msg) {
|
|
100
|
-
console.log('save catch', msg)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
//select all
|
|
104
|
-
let ss = await wo.select()
|
|
105
|
-
console.log('select all', ss)
|
|
106
|
-
|
|
107
|
-
//select
|
|
108
|
-
let so = await wo.select({ id: 'id-rosemary' })
|
|
109
|
-
console.log('select', so)
|
|
110
|
-
|
|
111
|
-
//select by $and, $gt, $lt
|
|
112
|
-
let spa = await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
|
|
113
|
-
console.log('select by $and, $gt, $lt', spa)
|
|
114
|
-
|
|
115
|
-
//select by $or, $gte, $lte
|
|
116
|
-
let spb = await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
|
|
117
|
-
console.log('select by $or, $gte, $lte', spb)
|
|
118
|
-
|
|
119
|
-
//select by $or, $and, $ne, $in, $nin
|
|
120
|
-
let spc = 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 } }] }] })
|
|
121
|
-
console.log('select by $or, $and, $ne, $in, $nin', spc)
|
|
122
|
-
|
|
123
|
-
//select by regex
|
|
124
|
-
let sr = await wo.select({ name: { $regex: 'PeT', $options: '
|
|
125
|
-
console.log('selectReg', sr)
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
// ]
|
|
169
|
-
// select by $
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
test
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
1
|
+
# w-orm-mongodb
|
|
2
|
+
An operator for mongodb in nodejs.
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
[](https://npmjs.org/package/w-orm-mongodb)
|
|
6
|
+
[](https://npmjs.org/package/w-orm-mongodb)
|
|
7
|
+
[](https://npmjs.org/package/w-orm-mongodb)
|
|
8
|
+
[](https://npmjs.org/package/w-orm-mongodb)
|
|
9
|
+
[](https://www.jsdelivr.com/package/npm/w-orm-mongodb)
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-orm-mongodb/WOrmMongodb.html).
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Using npm(ES6 module):
|
|
17
|
+
```alias
|
|
18
|
+
npm i w-orm-mongodb
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### Example for collection
|
|
22
|
+
> **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-basic.mjs)]
|
|
23
|
+
```alias
|
|
24
|
+
import WOrm from './src/WOrmMongodb.mjs'
|
|
25
|
+
//import WOrm from './dist/w-orm-mongodb.umd.js'
|
|
26
|
+
|
|
27
|
+
let opt = {
|
|
28
|
+
url: 'mongodb://username:password@127.0.0.1:27017',
|
|
29
|
+
db: 'worm',
|
|
30
|
+
cl: 'users',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let rs = [
|
|
34
|
+
{
|
|
35
|
+
id: 'id-peter',
|
|
36
|
+
name: 'peter',
|
|
37
|
+
value: 123,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'id-rosemary',
|
|
41
|
+
name: 'rosemary',
|
|
42
|
+
value: 123.456,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: '',
|
|
46
|
+
name: 'kettle',
|
|
47
|
+
value: 456,
|
|
48
|
+
},
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
let rsm = [
|
|
52
|
+
{
|
|
53
|
+
id: 'id-peter',
|
|
54
|
+
name: 'peter(modify)'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'id-rosemary',
|
|
58
|
+
name: 'rosemary(modify)'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: '',
|
|
62
|
+
name: 'kettle(modify)'
|
|
63
|
+
},
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
async function test() {
|
|
67
|
+
|
|
68
|
+
//wo
|
|
69
|
+
let wo = WOrm(opt)
|
|
70
|
+
|
|
71
|
+
//on
|
|
72
|
+
wo.on('change', function(mode, data, res) {
|
|
73
|
+
console.log('change', mode)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
//delAll
|
|
77
|
+
await wo.delAll()
|
|
78
|
+
.then(function(msg) {
|
|
79
|
+
console.log('delAll then', msg)
|
|
80
|
+
})
|
|
81
|
+
.catch(function(msg) {
|
|
82
|
+
console.log('delAll catch', msg)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
//insert
|
|
86
|
+
await wo.insert(rs)
|
|
87
|
+
.then(function(msg) {
|
|
88
|
+
console.log('insert then', msg)
|
|
89
|
+
})
|
|
90
|
+
.catch(function(msg) {
|
|
91
|
+
console.log('insert catch', msg)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
//save
|
|
95
|
+
await wo.save(rsm, { autoInsert: false })
|
|
96
|
+
.then(function(msg) {
|
|
97
|
+
console.log('save then', msg)
|
|
98
|
+
})
|
|
99
|
+
.catch(function(msg) {
|
|
100
|
+
console.log('save catch', msg)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
//select all
|
|
104
|
+
let ss = await wo.select()
|
|
105
|
+
console.log('select all', ss)
|
|
106
|
+
|
|
107
|
+
//select
|
|
108
|
+
let so = await wo.select({ id: 'id-rosemary' })
|
|
109
|
+
console.log('select', so)
|
|
110
|
+
|
|
111
|
+
//select by $and, $gt, $lt
|
|
112
|
+
let spa = await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
|
|
113
|
+
console.log('select by $and, $gt, $lt', spa)
|
|
114
|
+
|
|
115
|
+
//select by $or, $gte, $lte
|
|
116
|
+
let spb = await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
|
|
117
|
+
console.log('select by $or, $gte, $lte', spb)
|
|
118
|
+
|
|
119
|
+
//select by $or, $and, $ne, $in, $nin
|
|
120
|
+
let spc = 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 } }] }] })
|
|
121
|
+
console.log('select by $or, $and, $ne, $in, $nin', spc)
|
|
122
|
+
|
|
123
|
+
//select by regex, $options之合法flag僅有i、m、x、s
|
|
124
|
+
let sr = await wo.select({ name: { $regex: 'PeT', $options: 'i' } })
|
|
125
|
+
console.log('selectReg', sr)
|
|
126
|
+
|
|
127
|
+
//selectById, 由id直接查找單筆, 不需如select提取全部符合數據再處理
|
|
128
|
+
let sbi = await wo.selectById('id-rosemary')
|
|
129
|
+
console.log('selectById', sbi)
|
|
130
|
+
|
|
131
|
+
//selectById by id not existed
|
|
132
|
+
let sbn = await wo.selectById('id-not-existed')
|
|
133
|
+
console.log('selectById by id not existed', sbn)
|
|
134
|
+
|
|
135
|
+
//del
|
|
136
|
+
let d = ss.filter(function(v) {
|
|
137
|
+
return v.name === 'kettle'
|
|
138
|
+
})
|
|
139
|
+
await wo.del(d)
|
|
140
|
+
.then(function(msg) {
|
|
141
|
+
console.log('del then', msg)
|
|
142
|
+
})
|
|
143
|
+
.catch(function(msg) {
|
|
144
|
+
console.log('del catch', msg)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
}
|
|
148
|
+
test()
|
|
149
|
+
// change delAll
|
|
150
|
+
// delAll then { n: 0, nDeleted: 0, ok: 1 }
|
|
151
|
+
// change insert
|
|
152
|
+
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
153
|
+
// change save
|
|
154
|
+
// save then [
|
|
155
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
156
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
157
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 }
|
|
158
|
+
// ]
|
|
159
|
+
// select all [
|
|
160
|
+
// { id: 'id-peter', name: 'peter(modify)', value: 123 },
|
|
161
|
+
// { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
|
|
162
|
+
// {
|
|
163
|
+
// id: {random id},
|
|
164
|
+
// name: 'kettle',
|
|
165
|
+
// value: 456
|
|
166
|
+
// }
|
|
167
|
+
// ]
|
|
168
|
+
// select [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
|
|
169
|
+
// select by $and, $gt, $lt [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
|
|
170
|
+
// select by $or, $gte, $lte [
|
|
171
|
+
// {
|
|
172
|
+
// id: {random id},
|
|
173
|
+
// name: 'kettle',
|
|
174
|
+
// value: 456
|
|
175
|
+
// }
|
|
176
|
+
// ]
|
|
177
|
+
// select by $or, $and, $ne, $in, $nin [
|
|
178
|
+
// {
|
|
179
|
+
// id: 'id-rosemary',
|
|
180
|
+
// name: 'rosemary(modify)',
|
|
181
|
+
// value: 123.456
|
|
182
|
+
// },
|
|
183
|
+
// {
|
|
184
|
+
// id: {random id},
|
|
185
|
+
// name: 'kettle',
|
|
186
|
+
// value: 456
|
|
187
|
+
// }
|
|
188
|
+
// ]
|
|
189
|
+
// selectReg [ { id: 'id-peter', name: 'peter(modify)', value: 123 } ]
|
|
190
|
+
// selectById { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 }
|
|
191
|
+
// selectById by id not existed null
|
|
192
|
+
// change del
|
|
193
|
+
// del then [ { n: 1, nDeleted: 1, ok: 1 } ]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Return values
|
|
197
|
+
|
|
198
|
+
本套件屬`w-orm-*`系列,六個函數`select`、`selectById`、`insert`、`save`、`del`、`delAll`之回傳結構依系列統一規格:
|
|
199
|
+
|
|
200
|
+
```alias
|
|
201
|
+
select(find) → [ {...}, {...} ] 無符合為 []
|
|
202
|
+
selectById(id) → {...} | null
|
|
203
|
+
|
|
204
|
+
insert(data) → { n, nInserted, ok }
|
|
205
|
+
save(data, option) → [ { n, nInserted, nModified, ok }, ... ]
|
|
206
|
+
del(data) → [ { n, nDeleted, ok }, ... ]
|
|
207
|
+
delAll(find) → { n, nDeleted, ok }
|
|
208
|
+
|
|
209
|
+
單筆失敗 → { ..., ok: 0, err: '...' } 僅 save、del
|
|
210
|
+
整批失敗 → Promise.reject(err)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
各計數欄位之語義:
|
|
214
|
+
|
|
215
|
+
| 欄位 | 語義 |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `n` | `insert`為輸入筆數;`save`與`del`為id命中筆數(`0`或`1`);`delAll`為實際刪除筆數 |
|
|
218
|
+
| `nInserted` | 實際插入筆數 |
|
|
219
|
+
| `nModified` | 實際更新筆數。合併後內容與現值相同而未寫入者為`0` |
|
|
220
|
+
| `nDeleted` | 實際刪除筆數 |
|
|
221
|
+
| `ok` | `1`成功、`0`該筆失敗 |
|
|
222
|
+
| `err` | 失敗訊息,僅於`ok`為`0`時出現 |
|
|
223
|
+
|
|
224
|
+
判讀準則:
|
|
225
|
+
|
|
226
|
+
| 要判斷什麼 | 看什麼 |
|
|
227
|
+
|---|---|
|
|
228
|
+
| 這批有幾筆是新資料 | `insert`之`nInserted` |
|
|
229
|
+
| 這筆是不是新資料 | `save`之`nInserted === 1` |
|
|
230
|
+
| 這筆內容有沒有實際寫入 | `save`之`nModified === 1` |
|
|
231
|
+
| 這筆有沒有真的被刪 | `nDeleted` |
|
|
232
|
+
| 整批有沒有失敗 | Promise是否`reject` |
|
|
233
|
+
| 個別筆有沒有失敗 | 逐筆之`ok === 0`,訊息取`err` |
|
|
234
|
+
|
|
235
|
+
`save`之「內容相同」判定基準為**將待儲存物件合併進現值之後,結果與現值相同**,由MongoDB於伺服器端逐欄位比對。故僅給部份欄位且該些欄位值皆與現值相同時,`nModified`亦為`0`。
|
|
236
|
+
|
|
237
|
+
`del`對未帶有效`id`者不送查詢條件,直接回`ok: 0`並附`err`,以免`undefined`經序列化為`null`而誤刪`id`為`null`之數據。
|
|
238
|
+
|
|
239
|
+
## Concurrency
|
|
240
|
+
|
|
241
|
+
`insert`之「已存在則跳過」與`save`之「不遺失更新」,由MongoDB於單一文件操作內原子完成,**不須開啟transaction**(transaction另須replica set,standalone不支援)。
|
|
242
|
+
|
|
243
|
+
| 適用範圍 | 是否保證 |
|
|
244
|
+
|---|---|
|
|
245
|
+
| 單一行程內併發 | 是 |
|
|
246
|
+
| 跨行程併發 | 是 |
|
|
247
|
+
|
|
248
|
+
原子性全由MongoDB伺服器端提供,本套件每次操作各自建立連線、不持有跨呼叫狀態,故兩種範圍之保證相同。
|
|
249
|
+
|
|
250
|
+
實測依據(Windows 11 / Node.js 24.19.0 / mongodb driver 7.5.0 / MongoDB 8 單機):
|
|
251
|
+
|
|
252
|
+
- 單一行程內對同一`id`併發`insert` 10次,`nInserted`總和為`1`,資料表僅`1`筆。
|
|
253
|
+
- 單一行程內對同一全新`id`併發`save` 5次,`nInserted === 1`者恰`1`筆,資料表僅`1`筆,5次所給欄位全數保留。
|
|
254
|
+
- 跨行程(2個獨立node行程)各自對相同20個`id`執行`insert`,`nInserted`總和為`20`,資料表恰`20`筆。
|
|
255
|
+
- 跨行程(2個獨立node行程)各自對同一既有`id`寫入20個不同欄位,40個欄位全數保留且原欄位未遺失,資料表恰`1`筆。
|
|
256
|
+
|
|
257
|
+
GridFS亦同(`test/api-gfs.test.mjs`):
|
|
258
|
+
|
|
259
|
+
- 對同一`id`併發`insertGfs` 10次,`nInserted`總和為`1`,僅新增1筆files且**未殘留孤兒chunks**。
|
|
260
|
+
|
|
261
|
+
以上皆為測試案例,可以`npm test`複現。
|
|
262
|
+
|
|
263
|
+
`delAll`之`nDeleted`於併發下反映該次`deleteMany`實際刪除筆數,不保證等於呼叫當下符合條件之筆數。
|
|
264
|
+
|
|
265
|
+
## Upgrading
|
|
266
|
+
|
|
267
|
+
本套件會於`id`欄位建立唯一索引,此為`insert`之「已存在則跳過」與`save`之「不遺失更新」所必需,無法關閉。**若既有資料表內已存在重複`id`,建立索引會失敗,`insert`與`save`會直接`reject`。** 升級前請先清除重複數據:
|
|
268
|
+
|
|
269
|
+
```alias
|
|
270
|
+
// 找出重複id
|
|
271
|
+
db.users.aggregate([
|
|
272
|
+
{ $group: { _id: '$id', n: { $sum: 1 } } },
|
|
273
|
+
{ $match: { n: { $gt: 1 } } },
|
|
274
|
+
])
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
清除重複數據後即可正常使用。索引僅於首次寫入時建立一次,已存在同樣索引時MongoDB不會重建亦不報錯。
|
|
278
|
+
|
|
279
|
+
GridFS同理,`insertGfs`會於`<cl>.files`之`filename`欄位建立唯一索引:
|
|
280
|
+
|
|
281
|
+
```alias
|
|
282
|
+
// 找出重複id
|
|
283
|
+
db['usersGfs.files'].aggregate([
|
|
284
|
+
{ $group: { _id: '$filename', n: { $sum: 1 } } },
|
|
285
|
+
{ $match: { n: { $gt: 1 } } },
|
|
286
|
+
])
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`selectByIdGfs`、`delGfs`、`delAllGfs`皆不建立索引,故既有數據縱使尚存重複`id`亦可正常查詢與清除,`delGfs`會將同一`id`之多筆一併刪除並如實回報`nDeleted`。
|
|
290
|
+
|
|
291
|
+
#### Example for unique id and concurrency
|
|
292
|
+
> **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-unique.mjs)]
|
|
293
|
+
|
|
294
|
+
```alias
|
|
295
|
+
import WOrm from './src/WOrmMongodb.mjs'
|
|
296
|
+
//import WOrm from './dist/w-orm-mongodb.umd.js'
|
|
297
|
+
|
|
298
|
+
let opt = {
|
|
299
|
+
url: 'mongodb://username:password@127.0.0.1:27017',
|
|
300
|
+
db: 'worm',
|
|
301
|
+
cl: 'usersUnique',
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function test() {
|
|
305
|
+
|
|
306
|
+
//wo
|
|
307
|
+
let wo = WOrm(opt)
|
|
308
|
+
|
|
309
|
+
//delAll
|
|
310
|
+
await wo.delAll()
|
|
311
|
+
|
|
312
|
+
//insert, 同批含重複id時僅首筆成功
|
|
313
|
+
let ri = await wo.insert([
|
|
314
|
+
{ id: 'id-dup', name: 'dup-1' },
|
|
315
|
+
{ id: 'id-dup', name: 'dup-2' },
|
|
316
|
+
{ id: 'id-uniq', name: 'uniq' },
|
|
317
|
+
])
|
|
318
|
+
console.log('insert with duplicated id', ri)
|
|
319
|
+
console.log('selectById(id-dup)', await wo.selectById('id-dup'))
|
|
320
|
+
|
|
321
|
+
//insert, 對已存在id再插入則跳過而不覆寫
|
|
322
|
+
let re = await wo.insert({ id: 'id-dup', name: 'dup-3' })
|
|
323
|
+
console.log('insert existed id', re)
|
|
324
|
+
console.log('selectById(id-dup)', await wo.selectById('id-dup'))
|
|
325
|
+
|
|
326
|
+
//insert, 併發對同一id插入10次, nInserted總和為1
|
|
327
|
+
let rc = await Promise.all(Array.from({ length: 10 }, (v, k) => {
|
|
328
|
+
return wo.insert({ id: 'id-race', k })
|
|
329
|
+
}))
|
|
330
|
+
console.log('sum of nInserted by 10 concurrent insert', rc.reduce((sum, v) => sum + v.nInserted, 0))
|
|
331
|
+
console.log('records of id-race', (await wo.select({ id: 'id-race' })).length)
|
|
332
|
+
|
|
333
|
+
//save, 併發對同一全新id儲存不同欄位, 僅一次為插入, 各欄位皆保留
|
|
334
|
+
let rs = await Promise.all(Array.from({ length: 5 }, (v, k) => {
|
|
335
|
+
return wo.save({ id: 'id-new', [`f${k}`]: k })
|
|
336
|
+
}))
|
|
337
|
+
console.log('count of nInserted===1 by 5 concurrent save', rs.filter((v) => v[0].nInserted === 1).length)
|
|
338
|
+
console.log('records of id-new', (await wo.select({ id: 'id-new' })).length)
|
|
339
|
+
console.log('selectById(id-new)', await wo.selectById('id-new'))
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
test()
|
|
343
|
+
// insert with duplicated id { n: 3, nInserted: 2, ok: 1 }
|
|
344
|
+
// selectById(id-dup) { id: 'id-dup', name: 'dup-1' }
|
|
345
|
+
// insert existed id { n: 1, nInserted: 0, ok: 1 }
|
|
346
|
+
// selectById(id-dup) { id: 'id-dup', name: 'dup-1' }
|
|
347
|
+
// sum of nInserted by 10 concurrent insert 1
|
|
348
|
+
// records of id-race 1
|
|
349
|
+
// count of nInserted===1 by 5 concurrent save 1
|
|
350
|
+
// records of id-new 1
|
|
351
|
+
// selectById(id-new) { id: 'id-new', f0: 0, f1: 1, f2: 2, f4: 4, f3: 3 }
|
|
352
|
+
// 註: 併發儲存之各欄位皆會保留, 惟欄位順序取決於各次儲存之完成順序, 故每次執行不盡相同
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### Example for GridFS
|
|
356
|
+
> **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-gfs.mjs)]
|
|
357
|
+
|
|
358
|
+
GridFS函數之參數與回傳形狀比照一般操作,數據物件為`{ id, u8a }`:
|
|
359
|
+
|
|
360
|
+
| GridFS函數 | 對應一般函數 | 回傳 |
|
|
361
|
+
|---|---|---|
|
|
362
|
+
| `selectByIdGfs(id)` | `selectById` | `{ id, u8a }` 或 `null` |
|
|
363
|
+
| `insertGfs(data)` | `insert` | `{ n, nInserted, ok }` |
|
|
364
|
+
| `delGfs(data)` | `del` | `[ { n, nDeleted, ok }, ... ]` |
|
|
365
|
+
| `delAllGfs(find)` | `delAll` | `{ n, nDeleted, ok }` |
|
|
366
|
+
|
|
367
|
+
`insertGfs`同樣具備「已存在則跳過」語義,以`<cl>.files`之`filename`唯一索引達成,升級前提與一般操作相同。GridFS無法於單一原子操作內取代既有內容,故不提供`saveGfs`,更新請以`delGfs`後再`insertGfs`完成。
|
|
368
|
+
|
|
369
|
+
```alias
|
|
370
|
+
import WOrm from './src/WOrmMongodb.mjs'
|
|
371
|
+
//import WOrm from './dist/w-orm-mongodb.umd.js'
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
//GridFS函數之參數與回傳形狀比照一般操作:
|
|
375
|
+
//數據物件為{ id, u8a }, insertGfs收物件或陣列, delGfs收物件或陣列並回傳等長陣列
|
|
376
|
+
let opt = {
|
|
377
|
+
url: 'mongodb://username:password@127.0.0.1:27017',
|
|
378
|
+
db: 'worm',
|
|
379
|
+
cl: 'usersGfs',
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
//genU8a, 產生內容可複現之測試數據
|
|
383
|
+
function genU8a(n) {
|
|
384
|
+
let u8a = new Uint8Array(n)
|
|
385
|
+
for (let i = 0; i < n; i++) {
|
|
386
|
+
u8a[i] = i % 256
|
|
387
|
+
}
|
|
388
|
+
return u8a
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function test() {
|
|
392
|
+
|
|
393
|
+
//wo
|
|
394
|
+
let wo = WOrm(opt)
|
|
395
|
+
|
|
396
|
+
//on
|
|
397
|
+
wo.on('change', function(mode, data, res) {
|
|
398
|
+
console.log('change', mode)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
//u8a, 亦可為瀏覽器或nodejs取得之任何Uint8Array
|
|
402
|
+
let u8a = genU8a(1000)
|
|
403
|
+
|
|
404
|
+
//delAllGfs
|
|
405
|
+
await wo.delAllGfs()
|
|
406
|
+
.then(function(msg) {
|
|
407
|
+
console.log('delAllGfs then', msg)
|
|
408
|
+
})
|
|
409
|
+
.catch(function(msg) {
|
|
410
|
+
console.log('delAllGfs catch', msg)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
//insertGfs
|
|
414
|
+
let gi = await wo.insertGfs({ id: 'id-file', u8a })
|
|
415
|
+
console.log('insertGfs', gi)
|
|
416
|
+
|
|
417
|
+
//insertGfs, 已存在id者跳過且不覆寫
|
|
418
|
+
let gr = await wo.insertGfs({ id: 'id-file', u8a: genU8a(50) })
|
|
419
|
+
console.log('insertGfs existed id', gr)
|
|
420
|
+
|
|
421
|
+
//selectByIdGfs
|
|
422
|
+
let gs = await wo.selectByIdGfs('id-file')
|
|
423
|
+
console.log('selectByIdGfs id', gs.id)
|
|
424
|
+
console.log('selectByIdGfs u8a.length', gs.u8a.length)
|
|
425
|
+
console.log('selectByIdGfs u8a[0..3]', gs.u8a[0], gs.u8a[1], gs.u8a[2], gs.u8a[3])
|
|
426
|
+
|
|
427
|
+
//selectByIdGfs by id not existed
|
|
428
|
+
let gn = await wo.selectByIdGfs('id-not-existed')
|
|
429
|
+
console.log('selectByIdGfs by id not existed', gn)
|
|
430
|
+
|
|
431
|
+
//insertGfs, 一次插入多筆
|
|
432
|
+
let gm = await wo.insertGfs([
|
|
433
|
+
{ id: 'id-a', u8a: genU8a(10) },
|
|
434
|
+
{ id: 'id-b', u8a: genU8a(20) },
|
|
435
|
+
])
|
|
436
|
+
console.log('insertGfs multi', gm)
|
|
437
|
+
|
|
438
|
+
//delGfs
|
|
439
|
+
let gd = await wo.delGfs({ id: 'id-file' })
|
|
440
|
+
console.log('delGfs', gd)
|
|
441
|
+
|
|
442
|
+
//delGfs by id not existed
|
|
443
|
+
let gdn = await wo.delGfs({ id: 'id-not-existed' })
|
|
444
|
+
console.log('delGfs by id not existed', gdn)
|
|
445
|
+
|
|
446
|
+
//delAllGfs
|
|
447
|
+
let gda = await wo.delAllGfs()
|
|
448
|
+
console.log('delAllGfs', gda)
|
|
449
|
+
|
|
450
|
+
}
|
|
451
|
+
test()
|
|
452
|
+
// change delAllGfs
|
|
453
|
+
// delAllGfs then { n: 0, nDeleted: 0, ok: 1 }
|
|
454
|
+
// change insertGfs
|
|
455
|
+
// insertGfs { n: 1, nInserted: 1, ok: 1 }
|
|
456
|
+
// change insertGfs
|
|
457
|
+
// insertGfs existed id { n: 1, nInserted: 0, ok: 1 }
|
|
458
|
+
// selectByIdGfs id id-file
|
|
459
|
+
// selectByIdGfs u8a.length 1000
|
|
460
|
+
// selectByIdGfs u8a[0..3] 0 1 2 3
|
|
461
|
+
// selectByIdGfs by id not existed null
|
|
462
|
+
// change insertGfs
|
|
463
|
+
// insertGfs multi { n: 2, nInserted: 2, ok: 1 }
|
|
464
|
+
// change delGfs
|
|
465
|
+
// delGfs [ { n: 1, nDeleted: 1, ok: 1 } ]
|
|
466
|
+
// change delGfs
|
|
467
|
+
// delGfs by id not existed [ { n: 0, nDeleted: 0, ok: 1 } ]
|
|
468
|
+
// change delAllGfs
|
|
469
|
+
// delAllGfs { n: 2, nDeleted: 2, ok: 1 }
|
|
470
|
+
|
|
471
|
+
```
|