w-orm-postgresql 1.0.7 → 1.0.9
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 +443 -428
- package/dist/w-orm-postgresql.umd.js +2 -2
- package/dist/w-orm-postgresql.umd.js.map +1 -1
- package/docs/WOrmPostgresql.html +194 -14
- package/docs/WOrmPostgresql.mjs.html +313 -80
- package/docs/index.html +2 -2
- package/g-basic.mjs +23 -9
- package/package.json +2 -2
- package/src/WOrmPostgresql.mjs +311 -78
- package/test/api-postgresql.test.mjs +1458 -0
- package/test/basic.test.mjs +0 -681
|
@@ -0,0 +1,1458 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import net from 'net'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import pg from 'pg'
|
|
5
|
+
import { execFile, spawn } from 'child_process'
|
|
6
|
+
import { promisify } from 'util'
|
|
7
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
8
|
+
import WOrm from '../src/WOrmPostgresql.mjs'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
//測試自行創建docker容器供PostgreSQL服務, 測試結束即銷毀, 故僅需環境已安裝docker
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
let execFileAsync = promisify(execFile)
|
|
15
|
+
|
|
16
|
+
let ctName = 'worm-test-postgresql'
|
|
17
|
+
let ctImage = 'postgres:17-alpine'
|
|
18
|
+
let ctUser = 'username'
|
|
19
|
+
let ctPassword = 'password'
|
|
20
|
+
let ctDb = 'worm'
|
|
21
|
+
let ctPort = null //容器對外埠, 由getFreePort動態取得, 避免與既有PostgreSQL服務搶5432
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
let delay = (ms) => {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
setTimeout(resolve, ms)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//getFreePort, 由系統配發空閒埠
|
|
31
|
+
let getFreePort = () => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let srv = net.createServer()
|
|
34
|
+
srv.on('error', reject)
|
|
35
|
+
srv.listen(0, () => {
|
|
36
|
+
let port = srv.address().port
|
|
37
|
+
srv.close(() => {
|
|
38
|
+
resolve(port)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//docker, 以陣列傳參不經shell, 避免路徑與引號轉譯問題
|
|
45
|
+
let docker = (args) => {
|
|
46
|
+
return execFileAsync('docker', args)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//genUrl
|
|
50
|
+
let genUrl = () => {
|
|
51
|
+
return `postgresql://${ctUser}:${ctPassword}@127.0.0.1:${ctPort}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//genOpt
|
|
55
|
+
let genOpt = (cl = 'users', useCache = false) => {
|
|
56
|
+
return {
|
|
57
|
+
url: genUrl(),
|
|
58
|
+
db: ctDb,
|
|
59
|
+
cl,
|
|
60
|
+
useCache,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//probeReady, 以pg直連容器對外埠試跑SELECT 1
|
|
65
|
+
//不使用容器內pg_isready, 因postgres映像初始化期間會先啟動僅監聽unix socket之臨時服務,
|
|
66
|
+
//pg_isready預設亦走unix socket故會提早回報就緒, 直連TCP方為測試實際所走路徑
|
|
67
|
+
let probeReady = async () => {
|
|
68
|
+
let client = new pg.Client({ connectionString: `${genUrl()}/${ctDb}` })
|
|
69
|
+
let ok = false
|
|
70
|
+
try {
|
|
71
|
+
await client.connect()
|
|
72
|
+
await client.query('SELECT 1')
|
|
73
|
+
ok = true
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
ok = false
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await client.end().catch(() => {})
|
|
80
|
+
}
|
|
81
|
+
return ok
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//startContainer, 創建容器並等待服務就緒
|
|
85
|
+
//postgres映像以POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB即建妥帳號與資料庫, 無須另跑建置語句
|
|
86
|
+
let startContainer = async () => {
|
|
87
|
+
|
|
88
|
+
//check docker
|
|
89
|
+
await docker(['version', '--format', '{{.Server.Version}}'])
|
|
90
|
+
.catch(() => {
|
|
91
|
+
throw new Error('需先安裝並啟動docker才能執行postgresql測試')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
//清除前次殘留容器
|
|
95
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
96
|
+
|
|
97
|
+
//run
|
|
98
|
+
ctPort = await getFreePort()
|
|
99
|
+
await docker(['run', '-d', '--rm', '--name', ctName, '-e', `POSTGRES_USER=${ctUser}`, '-e', `POSTGRES_PASSWORD=${ctPassword}`, '-e', `POSTGRES_DB=${ctDb}`, '-p', `${ctPort}:5432`, ctImage])
|
|
100
|
+
|
|
101
|
+
//等待服務就緒, 首次需拉取映像故給予較長時間
|
|
102
|
+
let ready = false
|
|
103
|
+
for (let i = 0; i < 90; i++) {
|
|
104
|
+
let ok = await probeReady()
|
|
105
|
+
if (ok) {
|
|
106
|
+
ready = true
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
await delay(2000)
|
|
110
|
+
}
|
|
111
|
+
if (!ready) {
|
|
112
|
+
throw new Error('postgresql容器啟動逾時')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//stopContainer, 銷毀容器
|
|
118
|
+
let stopContainer = async () => {
|
|
119
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
before(async function() {
|
|
124
|
+
this.timeout(600000) //含拉取映像與服務啟動
|
|
125
|
+
await startContainer()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
after(async function() {
|
|
129
|
+
this.timeout(120000)
|
|
130
|
+
await stopContainer()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
describe('basic', function() {
|
|
135
|
+
let rt = null
|
|
136
|
+
let vans = {}
|
|
137
|
+
let vget = {}
|
|
138
|
+
|
|
139
|
+
before(async function () {
|
|
140
|
+
|
|
141
|
+
let opt = genOpt('users')
|
|
142
|
+
|
|
143
|
+
let rs = [
|
|
144
|
+
{
|
|
145
|
+
time: '2025-01-01T00:00:00Z',
|
|
146
|
+
name: 'peter',
|
|
147
|
+
value: 123,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
time: '2025-01-01T00:01:00Z',
|
|
151
|
+
name: 'rosemary',
|
|
152
|
+
value: 123.456,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
time: '2025-01-01T00:02:00Z',
|
|
156
|
+
name: 'kettle',
|
|
157
|
+
value: 456,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
time: '2025-01-01T00:03:00Z',
|
|
161
|
+
name: 'peter',
|
|
162
|
+
value: 200,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
time: '2025-01-01T00:04:00Z',
|
|
166
|
+
name: 'rosemary',
|
|
167
|
+
value: 123.1236,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
time: '2025-01-01T00:05:00Z',
|
|
171
|
+
name: 'kettle',
|
|
172
|
+
value: 488,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
time: '2025-01-01T00:06:00Z',
|
|
176
|
+
name: 'peter',
|
|
177
|
+
value: 125,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
time: '2025-01-01T00:07:00Z',
|
|
181
|
+
name: 'rosemary',
|
|
182
|
+
value: 124.76,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
time: '2025-01-01T00:08:00Z',
|
|
186
|
+
name: 'kettle',
|
|
187
|
+
value: 524,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
time: '2025-01-01T00:09:00Z',
|
|
191
|
+
name: 'peter',
|
|
192
|
+
value: 127,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
time: '2025-01-01T00:10:00Z',
|
|
196
|
+
name: 'rosemary',
|
|
197
|
+
value: 113.98,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
time: '2025-01-01T00:11:00Z',
|
|
201
|
+
name: 'kettle',
|
|
202
|
+
value: 447,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
time: '2025-01-01T00:12:00Z',
|
|
206
|
+
name: 'peter',
|
|
207
|
+
value: 131,
|
|
208
|
+
},
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
let rsm = [
|
|
212
|
+
{ //相同
|
|
213
|
+
time: '2025-01-01T00:09:00Z',
|
|
214
|
+
name: 'peter',
|
|
215
|
+
value: 127,
|
|
216
|
+
},
|
|
217
|
+
{ //name變更
|
|
218
|
+
time: '2025-01-01T00:10:00Z',
|
|
219
|
+
name: 'rosemary(modify)',
|
|
220
|
+
value: 113.98,
|
|
221
|
+
},
|
|
222
|
+
{ //name變更
|
|
223
|
+
time: '2025-01-01T00:11:00Z',
|
|
224
|
+
name: 'kettle(modify)',
|
|
225
|
+
value: 447,
|
|
226
|
+
},
|
|
227
|
+
{ //無name且value變更
|
|
228
|
+
time: '2025-01-01T00:12:00Z',
|
|
229
|
+
// name: 'peter',
|
|
230
|
+
value: 99,
|
|
231
|
+
},
|
|
232
|
+
{ //無time須新增且新增數據無value
|
|
233
|
+
time: '2025-01-01T00:13:00Z',
|
|
234
|
+
name: 'sandler',
|
|
235
|
+
},
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
let rsa = [
|
|
239
|
+
{
|
|
240
|
+
time: '2025-01-01T00:13:00Z',
|
|
241
|
+
name: 'sandler',
|
|
242
|
+
},
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
//wo
|
|
246
|
+
let wo = WOrm(opt)
|
|
247
|
+
|
|
248
|
+
//on
|
|
249
|
+
wo.on('change', function(mode, data, res) {
|
|
250
|
+
// console.log('change', mode)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await wo.createTable(opt.cl, 'time', {
|
|
254
|
+
time: '2000-01-01T00:00:00Z', //time
|
|
255
|
+
name: 'abc', //string
|
|
256
|
+
value: 0.1, //float
|
|
257
|
+
})
|
|
258
|
+
.then(function(msg) {
|
|
259
|
+
// console.log('createTable then', msg)
|
|
260
|
+
})
|
|
261
|
+
.catch(function(msg) {
|
|
262
|
+
// console.log('createTable catch', msg)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
//delAll
|
|
266
|
+
rt = null
|
|
267
|
+
// vans[1] = { ok: 1 }
|
|
268
|
+
await wo.delAll()
|
|
269
|
+
.then(function(msg) {
|
|
270
|
+
// console.log('delAll then', msg)
|
|
271
|
+
//考慮有不同初始狀態, 僅比對ok欄位
|
|
272
|
+
rt = {
|
|
273
|
+
ok: msg.ok,
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
.catch(function(msg) {
|
|
277
|
+
// console.log('delAll catch', msg)
|
|
278
|
+
rt = msg.toString()
|
|
279
|
+
})
|
|
280
|
+
vget[1] = rt
|
|
281
|
+
|
|
282
|
+
//insert
|
|
283
|
+
rt = null
|
|
284
|
+
// vans[2] = { n: 13, nInserted: 13, ok: 1 }
|
|
285
|
+
await wo.insert(rs)
|
|
286
|
+
.then(function(msg) {
|
|
287
|
+
// console.log('insert then', msg)
|
|
288
|
+
rt = msg
|
|
289
|
+
})
|
|
290
|
+
.catch(function(msg) {
|
|
291
|
+
// console.log('insert catch', msg)
|
|
292
|
+
rt = msg.toString()
|
|
293
|
+
})
|
|
294
|
+
vget[2] = rt
|
|
295
|
+
|
|
296
|
+
//save
|
|
297
|
+
rt = null
|
|
298
|
+
// vans[3] = [
|
|
299
|
+
// { n: 1, nModified: 0, ok: 1 },
|
|
300
|
+
// { n: 1, nModified: 1, ok: 1 },
|
|
301
|
+
// { n: 1, nModified: 1, ok: 1 },
|
|
302
|
+
// { n: 1, nModified: 1, ok: 1 },
|
|
303
|
+
// { n: 0, nModified: 0, ok: 1 }
|
|
304
|
+
// ]
|
|
305
|
+
await wo.save(rsm, { autoInsert: false })
|
|
306
|
+
.then(function(msg) {
|
|
307
|
+
// console.log('save then', msg)
|
|
308
|
+
rt = msg
|
|
309
|
+
})
|
|
310
|
+
.catch(function(msg) {
|
|
311
|
+
// console.log('save catch', msg)
|
|
312
|
+
rt = msg.toString()
|
|
313
|
+
})
|
|
314
|
+
vget[3] = rt
|
|
315
|
+
|
|
316
|
+
//select all
|
|
317
|
+
rt = null
|
|
318
|
+
// vans[4] = [
|
|
319
|
+
// { time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
320
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
321
|
+
// { time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
322
|
+
// { time: new Date('2025-01-01T00:03:00.000Z'), name: 'peter', value: 200 },
|
|
323
|
+
// { time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
324
|
+
// { time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
325
|
+
// { time: new Date('2025-01-01T00:06:00.000Z'), name: 'peter', value: 125 },
|
|
326
|
+
// { time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
327
|
+
// { time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
328
|
+
// { time: new Date('2025-01-01T00:09:00.000Z'), name: 'peter', value: 127 },
|
|
329
|
+
// { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 },
|
|
330
|
+
// { time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 },
|
|
331
|
+
// { time: new Date('2025-01-01T00:12:00.000Z'), name: 'peter', value: 99 }
|
|
332
|
+
// ]
|
|
333
|
+
await wo.select()
|
|
334
|
+
.then(function(msg) {
|
|
335
|
+
// console.log('select all then', msg)
|
|
336
|
+
rt = msg
|
|
337
|
+
})
|
|
338
|
+
.catch(function(msg) {
|
|
339
|
+
// console.log('select all catch', msg)
|
|
340
|
+
rt = msg.toString()
|
|
341
|
+
})
|
|
342
|
+
vget[4] = rt
|
|
343
|
+
|
|
344
|
+
//select all by cache
|
|
345
|
+
rt = null
|
|
346
|
+
// vans[5] = vans[4]
|
|
347
|
+
await wo.select()
|
|
348
|
+
.then(function(msg) {
|
|
349
|
+
// console.log('select all by cache then', msg)
|
|
350
|
+
rt = msg
|
|
351
|
+
})
|
|
352
|
+
.catch(function(msg) {
|
|
353
|
+
// console.log('select all by cache catch', msg)
|
|
354
|
+
rt = msg.toString()
|
|
355
|
+
})
|
|
356
|
+
vget[5] = rt
|
|
357
|
+
|
|
358
|
+
//select by name
|
|
359
|
+
rt = null
|
|
360
|
+
// vans[6] = [
|
|
361
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
362
|
+
// { time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
363
|
+
// { time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 }
|
|
364
|
+
// ]
|
|
365
|
+
await wo.select({ name: 'rosemary' })
|
|
366
|
+
.then(function(msg) {
|
|
367
|
+
// console.log('select all then', msg)
|
|
368
|
+
rt = msg
|
|
369
|
+
})
|
|
370
|
+
.catch(function(msg) {
|
|
371
|
+
// console.log('select all catch', msg)
|
|
372
|
+
rt = msg.toString()
|
|
373
|
+
})
|
|
374
|
+
vget[6] = rt
|
|
375
|
+
|
|
376
|
+
//selectByTime
|
|
377
|
+
rt = null
|
|
378
|
+
// vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
379
|
+
await wo.selectByTime('2025-01-01T00:10:00Z')
|
|
380
|
+
.then(function(msg) {
|
|
381
|
+
// console.log('selectByTime then', msg)
|
|
382
|
+
// selectByTime { time: 2025-01-01T00:10:00.000Z, name: 'rosemary(modify)', value: 113.98 }
|
|
383
|
+
rt = msg
|
|
384
|
+
})
|
|
385
|
+
.catch(function(msg) {
|
|
386
|
+
// console.log('selectByTime catch', msg)
|
|
387
|
+
rt = msg.toString()
|
|
388
|
+
})
|
|
389
|
+
vget[18] = rt
|
|
390
|
+
|
|
391
|
+
//selectByTime by time not existed
|
|
392
|
+
rt = null
|
|
393
|
+
// vans[19] = null
|
|
394
|
+
await wo.selectByTime('2024-01-01T00:00:00Z')
|
|
395
|
+
.then(function(msg) {
|
|
396
|
+
// console.log('selectByTime by time not existed then', msg)
|
|
397
|
+
// selectByTime by time not existed null
|
|
398
|
+
rt = msg
|
|
399
|
+
})
|
|
400
|
+
.catch(function(msg) {
|
|
401
|
+
// console.log('selectByTime by time not existed catch', msg)
|
|
402
|
+
rt = msg.toString()
|
|
403
|
+
})
|
|
404
|
+
vget[19] = rt
|
|
405
|
+
|
|
406
|
+
//selectByTime by time invalid, 未給有效主鍵值視為查無數據
|
|
407
|
+
rt = null
|
|
408
|
+
// vans[20] = null
|
|
409
|
+
await wo.selectByTime('abc')
|
|
410
|
+
.then(function(msg) {
|
|
411
|
+
// console.log('selectByTime by time invalid then', msg)
|
|
412
|
+
// selectByTime by time invalid null
|
|
413
|
+
rt = msg
|
|
414
|
+
})
|
|
415
|
+
.catch(function(msg) {
|
|
416
|
+
// console.log('selectByTime by time invalid catch', msg)
|
|
417
|
+
rt = msg.toString()
|
|
418
|
+
})
|
|
419
|
+
vget[20] = rt
|
|
420
|
+
|
|
421
|
+
//select by $and, $gt, $lt
|
|
422
|
+
rt = null
|
|
423
|
+
// vans[7] = [
|
|
424
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
425
|
+
// { time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
426
|
+
// { time: new Date('2025-01-01T00:06:00.000Z'), name: 'peter', value: 125 },
|
|
427
|
+
// { time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
428
|
+
// { time: new Date('2025-01-01T00:09:00.000Z'), name: 'peter', value: 127 }
|
|
429
|
+
// ]
|
|
430
|
+
await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
|
|
431
|
+
.then(function(msg) {
|
|
432
|
+
// console.log('select all then', msg)
|
|
433
|
+
rt = msg
|
|
434
|
+
})
|
|
435
|
+
.catch(function(msg) {
|
|
436
|
+
// console.log('select all catch', msg)
|
|
437
|
+
rt = msg.toString()
|
|
438
|
+
})
|
|
439
|
+
vget[7] = rt
|
|
440
|
+
|
|
441
|
+
//select by $or, $gte, $lte
|
|
442
|
+
rt = null
|
|
443
|
+
// vans[8] = [
|
|
444
|
+
// { time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
445
|
+
// { time: new Date('2025-01-01T00:03:00.000Z'), name: 'peter', value: 200 },
|
|
446
|
+
// { time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
447
|
+
// { time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
448
|
+
// { time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 }
|
|
449
|
+
// ]
|
|
450
|
+
await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
|
|
451
|
+
.then(function(msg) {
|
|
452
|
+
// console.log('select all then', msg)
|
|
453
|
+
rt = msg
|
|
454
|
+
})
|
|
455
|
+
.catch(function(msg) {
|
|
456
|
+
// console.log('select all catch', msg)
|
|
457
|
+
rt = msg.toString()
|
|
458
|
+
})
|
|
459
|
+
vget[8] = rt
|
|
460
|
+
|
|
461
|
+
//select by $or, $and, $ne, $in, $nin
|
|
462
|
+
rt = null
|
|
463
|
+
// vans[9] = [
|
|
464
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
465
|
+
// { time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
466
|
+
// { time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
467
|
+
// { time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
468
|
+
// { time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 }
|
|
469
|
+
// ]
|
|
470
|
+
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 } }] }] })
|
|
471
|
+
.then(function(msg) {
|
|
472
|
+
// console.log('select all then', msg)
|
|
473
|
+
rt = msg
|
|
474
|
+
})
|
|
475
|
+
.catch(function(msg) {
|
|
476
|
+
// console.log('select all catch', msg)
|
|
477
|
+
rt = msg.toString()
|
|
478
|
+
})
|
|
479
|
+
vget[9] = rt
|
|
480
|
+
|
|
481
|
+
// //select by regex
|
|
482
|
+
// rt = null
|
|
483
|
+
// vans[14] = []
|
|
484
|
+
// let sr = await wo.select({ name: { $regex: 'PeT', $options: '$i' } })
|
|
485
|
+
// .then(function(msg) {
|
|
486
|
+
// // console.log('select all then', msg)
|
|
487
|
+
// rt = msg
|
|
488
|
+
// })
|
|
489
|
+
// .catch(function(msg) {
|
|
490
|
+
// // console.log('select all catch', msg)
|
|
491
|
+
// rt = msg.toString()
|
|
492
|
+
// })
|
|
493
|
+
// vget[14] = rt
|
|
494
|
+
|
|
495
|
+
//save
|
|
496
|
+
rt = null
|
|
497
|
+
// vans[10] = [{ n: 1, nInserted: 1, ok: 1 }]
|
|
498
|
+
await wo.save(rsa, { autoInsert: true })
|
|
499
|
+
.then(function(msg) {
|
|
500
|
+
// console.log('save then', msg)
|
|
501
|
+
rt = msg
|
|
502
|
+
})
|
|
503
|
+
.catch(function(msg) {
|
|
504
|
+
// console.log('save catch', msg)
|
|
505
|
+
rt = msg.toString()
|
|
506
|
+
})
|
|
507
|
+
vget[10] = rt
|
|
508
|
+
|
|
509
|
+
//del
|
|
510
|
+
rt = null
|
|
511
|
+
// vans[11] = [{ n: 1, nDeleted: 0, ok: 1 }]
|
|
512
|
+
let d = {
|
|
513
|
+
time: '2024-01-01T00:00:00Z',
|
|
514
|
+
}
|
|
515
|
+
await wo.del(d)
|
|
516
|
+
.then(function(msg) {
|
|
517
|
+
// console.log('del then', msg)
|
|
518
|
+
rt = msg
|
|
519
|
+
})
|
|
520
|
+
.catch(function(msg) {
|
|
521
|
+
// console.log('del catch', msg)
|
|
522
|
+
rt = msg.toString()
|
|
523
|
+
})
|
|
524
|
+
vget[11] = rt
|
|
525
|
+
|
|
526
|
+
//del
|
|
527
|
+
rt = null
|
|
528
|
+
// vans[12] = [
|
|
529
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
530
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
531
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
532
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
533
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
534
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
535
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
536
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
537
|
+
// { n: 1, nDeleted: 1, ok: 1 },
|
|
538
|
+
// { n: 1, nDeleted: 1, ok: 1 }
|
|
539
|
+
// ]
|
|
540
|
+
let ss = await wo.select()
|
|
541
|
+
let ds = ss.filter(function(v) {
|
|
542
|
+
return v.name.indexOf('peter') >= 0 || v.name.indexOf('kettle') >= 0 || v.name.indexOf('sandler') >= 0
|
|
543
|
+
})
|
|
544
|
+
await wo.del(ds)
|
|
545
|
+
.then(function(msg) {
|
|
546
|
+
// console.log('del then', msg)
|
|
547
|
+
rt = msg
|
|
548
|
+
})
|
|
549
|
+
.catch(function(msg) {
|
|
550
|
+
// console.log('del catch', msg)
|
|
551
|
+
rt = msg.toString()
|
|
552
|
+
})
|
|
553
|
+
vget[12] = rt
|
|
554
|
+
|
|
555
|
+
//select all final
|
|
556
|
+
rt = null
|
|
557
|
+
// vans[13] = [
|
|
558
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
559
|
+
// { time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
560
|
+
// { time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
561
|
+
// { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
562
|
+
// ]
|
|
563
|
+
await wo.select()
|
|
564
|
+
.then(function(msg) {
|
|
565
|
+
// console.log('select all then', msg)
|
|
566
|
+
rt = msg
|
|
567
|
+
})
|
|
568
|
+
.catch(function(msg) {
|
|
569
|
+
// console.log('select all catch', msg)
|
|
570
|
+
rt = msg.toString()
|
|
571
|
+
})
|
|
572
|
+
vget[13] = rt
|
|
573
|
+
|
|
574
|
+
//清除數據, 後續cache測試共用users表
|
|
575
|
+
await wo.delAll()
|
|
576
|
+
|
|
577
|
+
//woc, 啟用useCache
|
|
578
|
+
let woc = WOrm({
|
|
579
|
+
...opt,
|
|
580
|
+
useCache: true,
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
let rsc = [
|
|
584
|
+
{
|
|
585
|
+
time: '2025-01-01T00:00:00Z',
|
|
586
|
+
name: 'peter',
|
|
587
|
+
value: 123,
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
time: '2025-01-01T00:01:00Z',
|
|
591
|
+
name: 'rosemary',
|
|
592
|
+
value: 123.456,
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
time: '2025-01-01T00:02:00Z',
|
|
596
|
+
name: 'kettle',
|
|
597
|
+
value: 456,
|
|
598
|
+
},
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
let rscm = [
|
|
602
|
+
{
|
|
603
|
+
time: '2025-01-01T00:01:00Z',
|
|
604
|
+
name: 'rosemary(modify)',
|
|
605
|
+
value: 654.321,
|
|
606
|
+
},
|
|
607
|
+
]
|
|
608
|
+
|
|
609
|
+
//insert
|
|
610
|
+
await woc.insert(rsc)
|
|
611
|
+
|
|
612
|
+
//select all (1st time, 從DB讀取並填入快取)
|
|
613
|
+
rt = null
|
|
614
|
+
// vans[14] = [
|
|
615
|
+
// { time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
616
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
617
|
+
// { time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
618
|
+
// ]
|
|
619
|
+
await woc.select()
|
|
620
|
+
.then(function(msg) {
|
|
621
|
+
// console.log('select 1st then', msg)
|
|
622
|
+
//依time排序, postgres UPDATE後物理順序可能改變
|
|
623
|
+
rt = msg.sort((a, b) => a.time - b.time)
|
|
624
|
+
})
|
|
625
|
+
.catch(function(msg) {
|
|
626
|
+
// console.log('select 1st catch', msg)
|
|
627
|
+
rt = msg.toString()
|
|
628
|
+
})
|
|
629
|
+
vget[14] = rt
|
|
630
|
+
|
|
631
|
+
//select all (2nd time, 命中快取, 內容須與第一次相同)
|
|
632
|
+
rt = null
|
|
633
|
+
// vans[15] = vans[14]
|
|
634
|
+
await woc.select()
|
|
635
|
+
.then(function(msg) {
|
|
636
|
+
// console.log('select 2nd then', msg)
|
|
637
|
+
rt = msg.sort((a, b) => a.time - b.time)
|
|
638
|
+
})
|
|
639
|
+
.catch(function(msg) {
|
|
640
|
+
// console.log('select 2nd catch', msg)
|
|
641
|
+
rt = msg.toString()
|
|
642
|
+
})
|
|
643
|
+
vget[15] = rt
|
|
644
|
+
|
|
645
|
+
//save (更新rosemary, 觸發快取重設)
|
|
646
|
+
rt = null
|
|
647
|
+
// vans[16] = [{ n: 1, nModified: 1, ok: 1 }]
|
|
648
|
+
await woc.save(rscm, { autoInsert: false })
|
|
649
|
+
.then(function(msg) {
|
|
650
|
+
// console.log('save then', msg)
|
|
651
|
+
rt = msg
|
|
652
|
+
})
|
|
653
|
+
.catch(function(msg) {
|
|
654
|
+
// console.log('save catch', msg)
|
|
655
|
+
rt = msg.toString()
|
|
656
|
+
})
|
|
657
|
+
vget[16] = rt
|
|
658
|
+
|
|
659
|
+
//select all (3rd time, 快取已重設, 從DB重新讀取, 須反映rosemary更新)
|
|
660
|
+
rt = null
|
|
661
|
+
// vans[17] = [
|
|
662
|
+
// { time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
663
|
+
// { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 },
|
|
664
|
+
// { time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
665
|
+
// ]
|
|
666
|
+
await woc.select()
|
|
667
|
+
.then(function(msg) {
|
|
668
|
+
// console.log('select 3rd then', msg)
|
|
669
|
+
rt = msg.sort((a, b) => a.time - b.time)
|
|
670
|
+
})
|
|
671
|
+
.catch(function(msg) {
|
|
672
|
+
// console.log('select 3rd catch', msg)
|
|
673
|
+
rt = msg.toString()
|
|
674
|
+
})
|
|
675
|
+
vget[17] = rt
|
|
676
|
+
|
|
677
|
+
//selectByTime (1st time, 從DB讀取並填入快取)
|
|
678
|
+
rt = null
|
|
679
|
+
// vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
680
|
+
await woc.selectByTime('2025-01-01T00:01:00Z')
|
|
681
|
+
.then(function(msg) {
|
|
682
|
+
// console.log('selectByTime 1st then', msg)
|
|
683
|
+
rt = msg
|
|
684
|
+
})
|
|
685
|
+
.catch(function(msg) {
|
|
686
|
+
// console.log('selectByTime 1st catch', msg)
|
|
687
|
+
rt = msg.toString()
|
|
688
|
+
})
|
|
689
|
+
vget[21] = rt
|
|
690
|
+
|
|
691
|
+
//selectByTime (2nd time, 命中快取, 內容須與第一次相同)
|
|
692
|
+
rt = null
|
|
693
|
+
// vans[22] = vans[21]
|
|
694
|
+
await woc.selectByTime('2025-01-01T00:01:00Z')
|
|
695
|
+
.then(function(msg) {
|
|
696
|
+
// console.log('selectByTime 2nd then', msg)
|
|
697
|
+
rt = msg
|
|
698
|
+
})
|
|
699
|
+
.catch(function(msg) {
|
|
700
|
+
// console.log('selectByTime 2nd catch', msg)
|
|
701
|
+
rt = msg.toString()
|
|
702
|
+
})
|
|
703
|
+
vget[22] = rt
|
|
704
|
+
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
vans[1] = { ok: 1 }
|
|
708
|
+
it(`should get ${JSON.stringify(vans[1])} for delAll`, async function() {
|
|
709
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
vans[2] = { n: 13, nInserted: 13, ok: 1 }
|
|
713
|
+
it(`should get ${JSON.stringify(vans[2])} for insert`, async function() {
|
|
714
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
vans[3] = [
|
|
718
|
+
{ n: 1, nInserted: 0, nModified: 0, ok: 1 },
|
|
719
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
720
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
721
|
+
{ n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
722
|
+
{ n: 0, nInserted: 0, nModified: 0, ok: 1 }
|
|
723
|
+
]
|
|
724
|
+
it(`should get ${JSON.stringify(vans[3])} for save(autoInsert=false)`, async function() {
|
|
725
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
vans[4] = [
|
|
729
|
+
{ time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
730
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
731
|
+
{ time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
732
|
+
{ time: new Date('2025-01-01T00:03:00.000Z'), name: 'peter', value: 200 },
|
|
733
|
+
{ time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
734
|
+
{ time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
735
|
+
{ time: new Date('2025-01-01T00:06:00.000Z'), name: 'peter', value: 125 },
|
|
736
|
+
{ time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
737
|
+
{ time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
738
|
+
{ time: new Date('2025-01-01T00:09:00.000Z'), name: 'peter', value: 127 },
|
|
739
|
+
{ time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 },
|
|
740
|
+
{ time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 },
|
|
741
|
+
{ time: new Date('2025-01-01T00:12:00.000Z'), name: 'peter', value: 99 }
|
|
742
|
+
]
|
|
743
|
+
it(`should get ${JSON.stringify(vans[4])} for select all`, async function() {
|
|
744
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
vans[5] = vans[4]
|
|
748
|
+
it(`should get ${JSON.stringify(vans[5])} for select all by cache`, async function() {
|
|
749
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
vans[6] = [
|
|
753
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
754
|
+
{ time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
755
|
+
{ time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 }
|
|
756
|
+
]
|
|
757
|
+
it(`should get ${JSON.stringify(vans[6])} for select by name`, async function() {
|
|
758
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
//vans[18]~vans[22]為後續新增之selectByTime, 為不更動既有編號故接續於末號之後
|
|
762
|
+
vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
763
|
+
it(`should get ${JSON.stringify(vans[18])} for selectByTime`, async function() {
|
|
764
|
+
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
vans[19] = null
|
|
768
|
+
it(`should get ${JSON.stringify(vans[19])} for selectByTime by time not existed`, async function() {
|
|
769
|
+
assert.strict.deepStrictEqual(vget[19], vans[19])
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
vans[20] = null
|
|
773
|
+
it(`should get ${JSON.stringify(vans[20])} for selectByTime by time invalid`, async function() {
|
|
774
|
+
assert.strict.deepStrictEqual(vget[20], vans[20])
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
vans[7] = [
|
|
778
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
779
|
+
{ time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
780
|
+
{ time: new Date('2025-01-01T00:06:00.000Z'), name: 'peter', value: 125 },
|
|
781
|
+
{ time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
782
|
+
{ time: new Date('2025-01-01T00:09:00.000Z'), name: 'peter', value: 127 }
|
|
783
|
+
]
|
|
784
|
+
it(`should get ${JSON.stringify(vans[7])} for select by $and, $gt, $lt`, async function() {
|
|
785
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
vans[8] = [
|
|
789
|
+
{ time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
790
|
+
{ time: new Date('2025-01-01T00:03:00.000Z'), name: 'peter', value: 200 },
|
|
791
|
+
{ time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
792
|
+
{ time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
793
|
+
{ time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 }
|
|
794
|
+
]
|
|
795
|
+
it(`should get ${JSON.stringify(vans[8])} for select by $or, $gte, $lte`, async function() {
|
|
796
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
vans[9] = [
|
|
800
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
801
|
+
{ time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
802
|
+
{ time: new Date('2025-01-01T00:05:00.000Z'), name: 'kettle', value: 488 },
|
|
803
|
+
{ time: new Date('2025-01-01T00:08:00.000Z'), name: 'kettle', value: 524 },
|
|
804
|
+
{ time: new Date('2025-01-01T00:11:00.000Z'), name: 'kettle(modify)', value: 447 }
|
|
805
|
+
]
|
|
806
|
+
it(`should get ${JSON.stringify(vans[9])} for select by $or, $and, $ne, $in, $nin`, async function() {
|
|
807
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
// vans[14] = []
|
|
811
|
+
// it(`should get ${JSON.stringify(vans[14])} for select by regex`, async function() {
|
|
812
|
+
// assert.strict.deepStrictEqual(vget[14], vans[14])
|
|
813
|
+
// })
|
|
814
|
+
|
|
815
|
+
vans[10] = [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }]
|
|
816
|
+
it(`should get ${JSON.stringify(vans[10])} for save(autoInsert=true)`, async function() {
|
|
817
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
vans[11] = [{ n: 0, nDeleted: 0, ok: 1 }]
|
|
821
|
+
it(`should get ${JSON.stringify(vans[11])} for del`, async function() {
|
|
822
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
vans[12] = [
|
|
826
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
827
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
828
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
829
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
830
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
831
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
832
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
833
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
834
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
835
|
+
{ n: 1, nDeleted: 1, ok: 1 }
|
|
836
|
+
]
|
|
837
|
+
it(`should get ${JSON.stringify(vans[12])} for del`, async function() {
|
|
838
|
+
assert.strict.deepStrictEqual(vget[12], vans[12])
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
vans[13] = [
|
|
842
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
843
|
+
{ time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
844
|
+
{ time: new Date('2025-01-01T00:07:00.000Z'), name: 'rosemary', value: 124.76 },
|
|
845
|
+
{ time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
846
|
+
]
|
|
847
|
+
it(`should get ${JSON.stringify(vans[13])} for select all final`, async function() {
|
|
848
|
+
assert.strict.deepStrictEqual(vget[13], vans[13])
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
vans[14] = [
|
|
852
|
+
{ time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
853
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
854
|
+
{ time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
855
|
+
]
|
|
856
|
+
it(`should get ${JSON.stringify(vans[14])} for select all (1st, fill cache)`, async function() {
|
|
857
|
+
assert.strict.deepStrictEqual(vget[14], vans[14])
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
vans[15] = vans[14]
|
|
861
|
+
it(`should get ${JSON.stringify(vans[15])} for select all (2nd, cache hit)`, async function() {
|
|
862
|
+
assert.strict.deepStrictEqual(vget[15], vans[15])
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
vans[16] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
|
|
866
|
+
it(`should get ${JSON.stringify(vans[16])} for save (invalidate cache)`, async function() {
|
|
867
|
+
assert.strict.deepStrictEqual(vget[16], vans[16])
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
vans[17] = [
|
|
871
|
+
{ time: new Date('2025-01-01T00:00:00.000Z'), name: 'peter', value: 123 },
|
|
872
|
+
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 },
|
|
873
|
+
{ time: new Date('2025-01-01T00:02:00.000Z'), name: 'kettle', value: 456 },
|
|
874
|
+
]
|
|
875
|
+
it(`should get ${JSON.stringify(vans[17])} for select all (3rd, reload after save)`, async function() {
|
|
876
|
+
assert.strict.deepStrictEqual(vget[17], vans[17])
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
880
|
+
it(`should get ${JSON.stringify(vans[21])} for selectByTime (1st, fill cache)`, async function() {
|
|
881
|
+
assert.strict.deepStrictEqual(vget[21], vans[21])
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
vans[22] = vans[21]
|
|
885
|
+
it(`should get ${JSON.stringify(vans[22])} for selectByTime (2nd, cache hit)`, async function() {
|
|
886
|
+
assert.strict.deepStrictEqual(vget[22], vans[22])
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
})
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
//insert與save之併發測試, 因各操作皆各自new PgClient另開連線, 原子性全由PostgreSQL提供,
|
|
893
|
+
//故同行程以Promise.all併發即等價於跨行程併發, 不須另起行程
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
describe('insert nInserted', function() {
|
|
897
|
+
let vans = {}
|
|
898
|
+
let vget = {}
|
|
899
|
+
|
|
900
|
+
before(async function() {
|
|
901
|
+
this.timeout(120000)
|
|
902
|
+
|
|
903
|
+
//wo
|
|
904
|
+
let cl = 'insertcnt'
|
|
905
|
+
let wo = WOrm(genOpt(cl))
|
|
906
|
+
|
|
907
|
+
//createTable
|
|
908
|
+
await wo.createTable(cl, 'time', {
|
|
909
|
+
time: '2000-01-01T00:00:00Z',
|
|
910
|
+
name: 'abc',
|
|
911
|
+
value: 0.1,
|
|
912
|
+
}).catch(() => {})
|
|
913
|
+
await wo.delAll()
|
|
914
|
+
|
|
915
|
+
//全新3筆
|
|
916
|
+
vget[1] = await wo.insert([
|
|
917
|
+
{ time: '2025-02-01T00:00:00Z', name: 'peter', value: 1 },
|
|
918
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
919
|
+
{ time: '2025-02-01T00:02:00Z', name: 'kettle', value: 3 },
|
|
920
|
+
])
|
|
921
|
+
|
|
922
|
+
//重複插入同3筆, 全數已存在故皆不插入
|
|
923
|
+
vget[2] = await wo.insert([
|
|
924
|
+
{ time: '2025-02-01T00:00:00Z', name: 'peter', value: 1 },
|
|
925
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
926
|
+
{ time: '2025-02-01T00:02:00Z', name: 'kettle', value: 3 },
|
|
927
|
+
])
|
|
928
|
+
|
|
929
|
+
//部份重複, 1筆已存在2筆為新
|
|
930
|
+
vget[3] = await wo.insert([
|
|
931
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
932
|
+
{ time: '2025-02-01T00:03:00Z', name: 'sandler', value: 4 },
|
|
933
|
+
{ time: '2025-02-01T00:04:00Z', name: 'joyce', value: 5 },
|
|
934
|
+
])
|
|
935
|
+
|
|
936
|
+
//同批含重複time, 僅首筆成功
|
|
937
|
+
vget[4] = await wo.insert([
|
|
938
|
+
{ time: '2025-02-01T00:05:00Z', name: 'dup-1', value: 6 },
|
|
939
|
+
{ time: '2025-02-01T00:05:00Z', name: 'dup-2', value: 7 },
|
|
940
|
+
{ time: '2025-02-01T00:06:00Z', name: 'uniq', value: 8 },
|
|
941
|
+
])
|
|
942
|
+
|
|
943
|
+
//併發對同一time插入10次, nInserted總和須為1
|
|
944
|
+
let rs = await Promise.all(Array.from({ length: 10 }, (v, k) => {
|
|
945
|
+
return wo.insert({ time: '2025-02-01T00:07:00Z', name: `race-${k}`, value: k })
|
|
946
|
+
}))
|
|
947
|
+
vget[5] = rs.reduce((sum, v) => sum + v.nInserted, 0)
|
|
948
|
+
|
|
949
|
+
//併發皆不得報錯
|
|
950
|
+
vget[6] = rs.filter((v) => v.ok !== 1).length
|
|
951
|
+
|
|
952
|
+
//最終筆數, 3+0+2+2+1=8
|
|
953
|
+
let ss = await wo.select()
|
|
954
|
+
vget[7] = ss.length
|
|
955
|
+
|
|
956
|
+
//同批重複者僅首筆入庫
|
|
957
|
+
let vdup = await wo.selectByTime('2025-02-01T00:05:00Z')
|
|
958
|
+
vget[8] = vdup.name
|
|
959
|
+
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
vans[1] = { n: 3, nInserted: 3, ok: 1 }
|
|
963
|
+
it(`should get ${JSON.stringify(vans[1])} for insert 3 new records`, async function() {
|
|
964
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
vans[2] = { n: 3, nInserted: 0, ok: 1 }
|
|
968
|
+
it(`should get ${JSON.stringify(vans[2])} for insert 3 existed records`, async function() {
|
|
969
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
970
|
+
})
|
|
971
|
+
|
|
972
|
+
vans[3] = { n: 3, nInserted: 2, ok: 1 }
|
|
973
|
+
it(`should get ${JSON.stringify(vans[3])} for insert 1 existed and 2 new records`, async function() {
|
|
974
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
vans[4] = { n: 3, nInserted: 2, ok: 1 }
|
|
978
|
+
it(`should get ${JSON.stringify(vans[4])} for insert with duplicated time in same batch`, async function() {
|
|
979
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
980
|
+
})
|
|
981
|
+
|
|
982
|
+
vans[5] = 1
|
|
983
|
+
it(`should get ${JSON.stringify(vans[5])} for sum of nInserted by 10 concurrent insert with same time`, async function() {
|
|
984
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
985
|
+
})
|
|
986
|
+
|
|
987
|
+
vans[6] = 0
|
|
988
|
+
it(`should get ${JSON.stringify(vans[6])} for count of ok!==1 in 10 concurrent insert`, async function() {
|
|
989
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
vans[7] = 8
|
|
993
|
+
it(`should get ${JSON.stringify(vans[7])} for records after all insert`, async function() {
|
|
994
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
vans[8] = 'dup-1'
|
|
998
|
+
it(`should get ${JSON.stringify(vans[8])} for keeping first one of duplicated time in same batch`, async function() {
|
|
999
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
describe('save concurrency', function() {
|
|
1006
|
+
let vans = {}
|
|
1007
|
+
let vget = {}
|
|
1008
|
+
|
|
1009
|
+
before(async function() {
|
|
1010
|
+
this.timeout(120000)
|
|
1011
|
+
|
|
1012
|
+
//wo
|
|
1013
|
+
let cl = 'saverace'
|
|
1014
|
+
let wo = WOrm(genOpt(cl))
|
|
1015
|
+
|
|
1016
|
+
//createTable
|
|
1017
|
+
await wo.createTable(cl, 'time', {
|
|
1018
|
+
time: '2000-01-01T00:00:00Z',
|
|
1019
|
+
name: 'abc',
|
|
1020
|
+
value: 0.1,
|
|
1021
|
+
}).catch(() => {})
|
|
1022
|
+
await wo.delAll()
|
|
1023
|
+
|
|
1024
|
+
//併發save對既有time之不同欄位, 各欄位皆須保留
|
|
1025
|
+
let tMerge = '2025-03-01T00:00:00Z'
|
|
1026
|
+
await wo.insert({ time: tMerge, name: 'origin', value: 1 })
|
|
1027
|
+
await Promise.all([
|
|
1028
|
+
wo.save({ time: tMerge, name: 'merge-name' }),
|
|
1029
|
+
wo.save({ time: tMerge, value: 99 }),
|
|
1030
|
+
])
|
|
1031
|
+
vget[1] = await wo.selectByTime(tMerge)
|
|
1032
|
+
|
|
1033
|
+
//併發save對全新time, autoInsert僅一次且不得報錯
|
|
1034
|
+
let tNew = '2025-03-01T00:01:00Z'
|
|
1035
|
+
let rsn = await Promise.all(Array.from({ length: 5 }, (v, k) => {
|
|
1036
|
+
return wo.save({ time: tNew, name: `new-${k}` })
|
|
1037
|
+
}))
|
|
1038
|
+
vget[2] = rsn.filter((v) => v[0].nInserted === 1).length
|
|
1039
|
+
vget[3] = rsn.filter((v) => v[0].ok !== 1).length
|
|
1040
|
+
vget[4] = (await wo.select({ time: tNew })).length
|
|
1041
|
+
|
|
1042
|
+
//save內容相同不更新
|
|
1043
|
+
let tSame = '2025-03-01T00:02:00Z'
|
|
1044
|
+
await wo.insert({ time: tSame, name: 'same', value: 5 })
|
|
1045
|
+
vget[5] = await wo.save({ time: tSame, name: 'same', value: 5 })
|
|
1046
|
+
|
|
1047
|
+
//save內容不同須更新, 未給之欄位須保留
|
|
1048
|
+
let tDiff = '2025-03-01T00:03:00Z'
|
|
1049
|
+
await wo.insert({ time: tDiff, name: 'diff', value: 5 })
|
|
1050
|
+
vget[6] = await wo.save({ time: tDiff, value: 6 })
|
|
1051
|
+
vget[7] = await wo.selectByTime(tDiff)
|
|
1052
|
+
|
|
1053
|
+
//save(autoInsert=false)對不存在之time不得插入
|
|
1054
|
+
let tNone = '2025-03-01T00:04:00Z'
|
|
1055
|
+
vget[8] = await wo.save({ time: tNone, name: 'none' }, { autoInsert: false })
|
|
1056
|
+
vget[9] = await wo.selectByTime(tNone)
|
|
1057
|
+
|
|
1058
|
+
//併發save(autoInsert=false)對既有time之不同欄位, 各欄位皆須保留且不得報錯
|
|
1059
|
+
let tUpd = '2025-03-01T00:05:00Z'
|
|
1060
|
+
await wo.insert({ time: tUpd, name: 'origin', value: 1 })
|
|
1061
|
+
let rsu = await Promise.all([
|
|
1062
|
+
wo.save({ time: tUpd, name: 'upd-name' }, { autoInsert: false }),
|
|
1063
|
+
wo.save({ time: tUpd, value: 88 }, { autoInsert: false }),
|
|
1064
|
+
])
|
|
1065
|
+
vget[10] = rsu.filter((v) => v[0].ok !== 1).length
|
|
1066
|
+
vget[11] = await wo.selectByTime(tUpd)
|
|
1067
|
+
|
|
1068
|
+
})
|
|
1069
|
+
|
|
1070
|
+
vans[1] = { time: new Date('2025-03-01T00:00:00.000Z'), name: 'merge-name', value: 99 }
|
|
1071
|
+
it(`should get ${JSON.stringify(vans[1])} for fields after 2 concurrent save with different field`, async function() {
|
|
1072
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
vans[2] = 1
|
|
1076
|
+
it(`should get ${JSON.stringify(vans[2])} for count of nInserted===1 in 5 concurrent save with time not existed`, async function() {
|
|
1077
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
vans[3] = 0
|
|
1081
|
+
it(`should get ${JSON.stringify(vans[3])} for count of ok!==1 in 5 concurrent save with time not existed`, async function() {
|
|
1082
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
vans[4] = 1
|
|
1086
|
+
it(`should get ${JSON.stringify(vans[4])} for records after 5 concurrent save with time not existed`, async function() {
|
|
1087
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1088
|
+
})
|
|
1089
|
+
|
|
1090
|
+
vans[5] = [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1091
|
+
it(`should get ${JSON.stringify(vans[5])} for save with same content`, async function() {
|
|
1092
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
vans[6] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
|
|
1096
|
+
it(`should get ${JSON.stringify(vans[6])} for save with different content`, async function() {
|
|
1097
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1098
|
+
})
|
|
1099
|
+
|
|
1100
|
+
vans[7] = { time: new Date('2025-03-01T00:03:00.000Z'), name: 'diff', value: 6 }
|
|
1101
|
+
it(`should get ${JSON.stringify(vans[7])} for keeping field not given in save`, async function() {
|
|
1102
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
1103
|
+
})
|
|
1104
|
+
|
|
1105
|
+
vans[8] = [{ n: 0, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1106
|
+
it(`should get ${JSON.stringify(vans[8])} for save(autoInsert=false) with time not existed`, async function() {
|
|
1107
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1108
|
+
})
|
|
1109
|
+
|
|
1110
|
+
vans[9] = null
|
|
1111
|
+
it(`should get ${JSON.stringify(vans[9])} for record after save(autoInsert=false) with time not existed`, async function() {
|
|
1112
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
1113
|
+
})
|
|
1114
|
+
|
|
1115
|
+
vans[10] = 0
|
|
1116
|
+
it(`should get ${JSON.stringify(vans[10])} for count of ok!==1 in 2 concurrent save(autoInsert=false)`, async function() {
|
|
1117
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
1118
|
+
})
|
|
1119
|
+
|
|
1120
|
+
vans[11] = { time: new Date('2025-03-01T00:05:00.000Z'), name: 'upd-name', value: 88 }
|
|
1121
|
+
it(`should get ${JSON.stringify(vans[11])} for fields after 2 concurrent save(autoInsert=false) with different field`, async function() {
|
|
1122
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
})
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
describe('spec compliance', function() {
|
|
1129
|
+
let vans = {}
|
|
1130
|
+
let vget = {}
|
|
1131
|
+
|
|
1132
|
+
before(async function() {
|
|
1133
|
+
this.timeout(120000)
|
|
1134
|
+
|
|
1135
|
+
//wo
|
|
1136
|
+
let cl = 'specok'
|
|
1137
|
+
let wo = WOrm(genOpt(cl))
|
|
1138
|
+
|
|
1139
|
+
//createTable
|
|
1140
|
+
await wo.createTable(cl, 'time', {
|
|
1141
|
+
time: '2000-01-01T00:00:00Z',
|
|
1142
|
+
name: 'abc',
|
|
1143
|
+
value: 0.1,
|
|
1144
|
+
}).catch(() => {})
|
|
1145
|
+
await wo.delAll()
|
|
1146
|
+
|
|
1147
|
+
//save只給部份欄位且值皆與現值相同, 合併後等於現值故不寫入
|
|
1148
|
+
let tPart = '2025-04-01T00:00:00Z'
|
|
1149
|
+
await wo.insert({ time: tPart, name: 'peter', value: 5 })
|
|
1150
|
+
vget[1] = await wo.save({ time: tPart, value: 5 })
|
|
1151
|
+
vget[2] = await wo.selectByTime(tPart)
|
|
1152
|
+
|
|
1153
|
+
//save單筆失敗不中斷整批, 失敗筆須帶err
|
|
1154
|
+
let tOk = '2025-04-01T00:01:00Z'
|
|
1155
|
+
let tBad = '2025-04-01T00:02:00Z'
|
|
1156
|
+
let rsf = await wo.save([
|
|
1157
|
+
{ time: tOk, name: 'ok' },
|
|
1158
|
+
{ time: tBad, notexistcol: 1 },
|
|
1159
|
+
])
|
|
1160
|
+
vget[3] = rsf.length
|
|
1161
|
+
vget[4] = rsf[0]
|
|
1162
|
+
vget[5] = {
|
|
1163
|
+
n: rsf[1].n,
|
|
1164
|
+
nInserted: rsf[1].nInserted,
|
|
1165
|
+
nModified: rsf[1].nModified,
|
|
1166
|
+
ok: rsf[1].ok,
|
|
1167
|
+
hasErr: isestr(rsf[1].err),
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
//del未帶有效主鍵者回ok:0且不中斷整批
|
|
1171
|
+
let tDel = '2025-04-01T00:03:00Z'
|
|
1172
|
+
await wo.insert({ time: tDel, name: 'todel' })
|
|
1173
|
+
let rsd = await wo.del([
|
|
1174
|
+
{ time: tDel },
|
|
1175
|
+
{ name: 'no-time' },
|
|
1176
|
+
])
|
|
1177
|
+
vget[6] = rsd.length
|
|
1178
|
+
vget[7] = rsd[0]
|
|
1179
|
+
vget[8] = {
|
|
1180
|
+
n: rsd[1].n,
|
|
1181
|
+
nDeleted: rsd[1].nDeleted,
|
|
1182
|
+
ok: rsd[1].ok,
|
|
1183
|
+
hasErr: isestr(rsd[1].err),
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
//T6例外, insert與save未帶有效time時不補值而reject
|
|
1187
|
+
vget[9] = await wo.insert({ name: 'no-time' })
|
|
1188
|
+
.then(() => 'resolved')
|
|
1189
|
+
.catch(() => 'rejected')
|
|
1190
|
+
vget[10] = await wo.save({ name: 'no-time' })
|
|
1191
|
+
.then(() => 'resolved')
|
|
1192
|
+
.catch(() => 'rejected')
|
|
1193
|
+
|
|
1194
|
+
//T5輸入無效
|
|
1195
|
+
vget[11] = await wo.insert(null)
|
|
1196
|
+
vget[12] = await wo.save(null)
|
|
1197
|
+
vget[13] = await wo.del(null)
|
|
1198
|
+
|
|
1199
|
+
//delAll帶條件且僅部份命中
|
|
1200
|
+
let clPart = 'specdelall'
|
|
1201
|
+
let woPart = WOrm(genOpt(clPart))
|
|
1202
|
+
await woPart.createTable(clPart, 'time', {
|
|
1203
|
+
time: '2000-01-01T00:00:00Z',
|
|
1204
|
+
name: 'abc',
|
|
1205
|
+
value: 0.1,
|
|
1206
|
+
}).catch(() => {})
|
|
1207
|
+
await woPart.delAll()
|
|
1208
|
+
await woPart.insert([
|
|
1209
|
+
{ time: '2025-05-01T00:00:00Z', name: 'aa', value: 1 },
|
|
1210
|
+
{ time: '2025-05-01T00:01:00Z', name: 'aa', value: 2 },
|
|
1211
|
+
{ time: '2025-05-01T00:02:00Z', name: 'bb', value: 3 },
|
|
1212
|
+
{ time: '2025-05-01T00:03:00Z', name: 'bb', value: 4 },
|
|
1213
|
+
])
|
|
1214
|
+
vget[16] = await woPart.delAll({ name: 'aa' })
|
|
1215
|
+
vget[17] = (await woPart.select()).length
|
|
1216
|
+
|
|
1217
|
+
//delAll條件無命中
|
|
1218
|
+
vget[18] = await woPart.delAll({ name: 'notexist' })
|
|
1219
|
+
|
|
1220
|
+
})
|
|
1221
|
+
|
|
1222
|
+
vans[1] = [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1223
|
+
it(`should get ${JSON.stringify(vans[1])} for save with partial fields all equal to current`, async function() {
|
|
1224
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1225
|
+
})
|
|
1226
|
+
|
|
1227
|
+
vans[2] = { time: new Date('2025-04-01T00:00:00.000Z'), name: 'peter', value: 5 }
|
|
1228
|
+
it(`should get ${JSON.stringify(vans[2])} for record unchanged after save with partial fields`, async function() {
|
|
1229
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
vans[3] = 2
|
|
1233
|
+
it(`should get ${JSON.stringify(vans[3])} for length of save result with 1 failed record`, async function() {
|
|
1234
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1235
|
+
})
|
|
1236
|
+
|
|
1237
|
+
vans[4] = { n: 1, nInserted: 1, nModified: 0, ok: 1 }
|
|
1238
|
+
it(`should get ${JSON.stringify(vans[4])} for succeeded record in save with 1 failed record`, async function() {
|
|
1239
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
vans[5] = { n: 1, nInserted: 0, nModified: 0, ok: 0, hasErr: true }
|
|
1243
|
+
it(`should get ${JSON.stringify(vans[5])} for failed record in save`, async function() {
|
|
1244
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
vans[6] = 2
|
|
1248
|
+
it(`should get ${JSON.stringify(vans[6])} for length of del result with 1 invalid time`, async function() {
|
|
1249
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
vans[7] = { n: 1, nDeleted: 1, ok: 1 }
|
|
1253
|
+
it(`should get ${JSON.stringify(vans[7])} for succeeded record in del with 1 invalid time`, async function() {
|
|
1254
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
1255
|
+
})
|
|
1256
|
+
|
|
1257
|
+
vans[8] = { n: 0, nDeleted: 0, ok: 0, hasErr: true }
|
|
1258
|
+
it(`should get ${JSON.stringify(vans[8])} for record without valid time in del`, async function() {
|
|
1259
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1260
|
+
})
|
|
1261
|
+
|
|
1262
|
+
vans[9] = 'rejected'
|
|
1263
|
+
it(`should get ${JSON.stringify(vans[9])} for insert without valid time`, async function() {
|
|
1264
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
1265
|
+
})
|
|
1266
|
+
|
|
1267
|
+
vans[10] = 'rejected'
|
|
1268
|
+
it(`should get ${JSON.stringify(vans[10])} for save without valid time`, async function() {
|
|
1269
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
1270
|
+
})
|
|
1271
|
+
|
|
1272
|
+
vans[11] = { n: 0, nInserted: 0, ok: 1 }
|
|
1273
|
+
it(`should get ${JSON.stringify(vans[11])} for insert with invalid data`, async function() {
|
|
1274
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
1275
|
+
})
|
|
1276
|
+
|
|
1277
|
+
vans[12] = []
|
|
1278
|
+
it(`should get ${JSON.stringify(vans[12])} for save with invalid data`, async function() {
|
|
1279
|
+
assert.strict.deepStrictEqual(vget[12], vans[12])
|
|
1280
|
+
})
|
|
1281
|
+
|
|
1282
|
+
vans[13] = []
|
|
1283
|
+
it(`should get ${JSON.stringify(vans[13])} for del with invalid data`, async function() {
|
|
1284
|
+
assert.strict.deepStrictEqual(vget[13], vans[13])
|
|
1285
|
+
})
|
|
1286
|
+
|
|
1287
|
+
vans[16] = { n: 2, nDeleted: 2, ok: 1 }
|
|
1288
|
+
it(`should get ${JSON.stringify(vans[16])} for delAll with find matched partially`, async function() {
|
|
1289
|
+
assert.strict.deepStrictEqual(vget[16], vans[16])
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
vans[17] = 2
|
|
1293
|
+
it(`should get ${JSON.stringify(vans[17])} for records after delAll with find matched partially`, async function() {
|
|
1294
|
+
assert.strict.deepStrictEqual(vget[17], vans[17])
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
vans[18] = { n: 0, nDeleted: 0, ok: 1 }
|
|
1298
|
+
it(`should get ${JSON.stringify(vans[18])} for delAll with find matched nothing`, async function() {
|
|
1299
|
+
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
1300
|
+
})
|
|
1301
|
+
|
|
1302
|
+
})
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
//runProc, 另起行程對同一資料表操作, 用於驗證跨行程之原子性
|
|
1306
|
+
//子行程以--input-type=module執行, 並以pathToFileURL轉換src路徑, 避免專案路徑含非ASCII字元而無法import
|
|
1307
|
+
function runProc(tag, opt, mode, payload) {
|
|
1308
|
+
return new Promise(function(resolve) {
|
|
1309
|
+
let code = `
|
|
1310
|
+
import { pathToFileURL } from 'url'
|
|
1311
|
+
let { default: WOrm } = await import(pathToFileURL(process.env.SRC).href)
|
|
1312
|
+
let wo = WOrm({ url: process.env.URL, db: process.env.DB, cl: process.env.CL })
|
|
1313
|
+
let payload = JSON.parse(process.env.PAYLOAD)
|
|
1314
|
+
let tag = process.env.TAG
|
|
1315
|
+
if (process.env.MODE === 'insert') {
|
|
1316
|
+
let n = 0
|
|
1317
|
+
for (let time of payload) {
|
|
1318
|
+
let r = await wo.insert({ time, name: tag, value: 1 })
|
|
1319
|
+
n += r.nInserted
|
|
1320
|
+
}
|
|
1321
|
+
console.log(JSON.stringify({ tag, nInserted: n }))
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
let nInserted = 0
|
|
1325
|
+
let nModified = 0
|
|
1326
|
+
for (let f of payload.fields) {
|
|
1327
|
+
let r = await wo.save({ time: payload.time, [f]: tag })
|
|
1328
|
+
nInserted += r[0].nInserted
|
|
1329
|
+
nModified += r[0].nModified
|
|
1330
|
+
}
|
|
1331
|
+
console.log(JSON.stringify({ tag, nInserted, nModified }))
|
|
1332
|
+
}
|
|
1333
|
+
`
|
|
1334
|
+
let out = ''
|
|
1335
|
+
let p = spawn(process.execPath, ['--input-type=module', '-e', code], {
|
|
1336
|
+
shell: false,
|
|
1337
|
+
env: {
|
|
1338
|
+
...process.env,
|
|
1339
|
+
SRC: path.resolve('./src/WOrmPostgresql.mjs'),
|
|
1340
|
+
URL: opt.url,
|
|
1341
|
+
DB: opt.db,
|
|
1342
|
+
CL: opt.cl,
|
|
1343
|
+
MODE: mode,
|
|
1344
|
+
PAYLOAD: JSON.stringify(payload),
|
|
1345
|
+
TAG: tag,
|
|
1346
|
+
},
|
|
1347
|
+
})
|
|
1348
|
+
p.stdout.on('data', function(d) {
|
|
1349
|
+
out += d.toString()
|
|
1350
|
+
})
|
|
1351
|
+
p.stderr.on('data', function(d) {
|
|
1352
|
+
out += d.toString()
|
|
1353
|
+
})
|
|
1354
|
+
p.on('close', function() {
|
|
1355
|
+
let r = null
|
|
1356
|
+
try {
|
|
1357
|
+
r = JSON.parse(out.trim())
|
|
1358
|
+
}
|
|
1359
|
+
catch (err) {
|
|
1360
|
+
r = { tag, error: out.trim() }
|
|
1361
|
+
}
|
|
1362
|
+
resolve(r)
|
|
1363
|
+
})
|
|
1364
|
+
})
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
describe('cross-process concurrency', function() {
|
|
1369
|
+
let vans = {}
|
|
1370
|
+
let vget = {}
|
|
1371
|
+
|
|
1372
|
+
before(async function() {
|
|
1373
|
+
this.timeout(300000)
|
|
1374
|
+
|
|
1375
|
+
//跨行程insert, 兩行程各自對相同20個time插入, nInserted總和須為20且資料表僅20筆
|
|
1376
|
+
let clIns = 'xprocinsert'
|
|
1377
|
+
let optIns = genOpt(clIns)
|
|
1378
|
+
let woIns = WOrm(optIns)
|
|
1379
|
+
await woIns.createTable(clIns, 'time', {
|
|
1380
|
+
time: '2000-01-01T00:00:00Z',
|
|
1381
|
+
name: 'abc',
|
|
1382
|
+
value: 0.1,
|
|
1383
|
+
}).catch(() => {})
|
|
1384
|
+
await woIns.delAll()
|
|
1385
|
+
|
|
1386
|
+
let times = Array.from({ length: 20 }, (v, k) => {
|
|
1387
|
+
return `2025-06-01T00:${`${k}`.padStart(2, '0')}:00Z`
|
|
1388
|
+
})
|
|
1389
|
+
let rps = await Promise.all([
|
|
1390
|
+
runProc('p1', optIns, 'insert', times),
|
|
1391
|
+
runProc('p2', optIns, 'insert', times),
|
|
1392
|
+
])
|
|
1393
|
+
vget[1] = rps.filter((v) => v.error !== undefined).length
|
|
1394
|
+
vget[2] = rps.reduce((sum, v) => sum + (v.nInserted || 0), 0)
|
|
1395
|
+
vget[3] = (await woIns.select()).length
|
|
1396
|
+
|
|
1397
|
+
//跨行程save對同一time之不同欄位, 兩行程各寫5欄, 10欄須全數保留
|
|
1398
|
+
let clSav = 'xprocsave'
|
|
1399
|
+
let optSav = genOpt(clSav)
|
|
1400
|
+
let woSav = WOrm(optSav)
|
|
1401
|
+
let vSample = { time: '2000-01-01T00:00:00Z' }
|
|
1402
|
+
Array.from({ length: 10 }, (v, k) => k).forEach((k) => {
|
|
1403
|
+
vSample[`f${k}`] = 'abc'
|
|
1404
|
+
})
|
|
1405
|
+
await woSav.createTable(clSav, 'time', vSample).catch(() => {})
|
|
1406
|
+
await woSav.delAll()
|
|
1407
|
+
|
|
1408
|
+
let tSav = '2025-06-02T00:00:00Z'
|
|
1409
|
+
let rpss = await Promise.all([
|
|
1410
|
+
runProc('p1', optSav, 'save', { time: tSav, fields: ['f0', 'f1', 'f2', 'f3', 'f4'] }),
|
|
1411
|
+
runProc('p2', optSav, 'save', { time: tSav, fields: ['f5', 'f6', 'f7', 'f8', 'f9'] }),
|
|
1412
|
+
])
|
|
1413
|
+
vget[4] = rpss.filter((v) => v.error !== undefined).length
|
|
1414
|
+
|
|
1415
|
+
//兩行程對同一新time各自save, 恰有一方之nInserted為1
|
|
1416
|
+
vget[5] = rpss.reduce((sum, v) => sum + (v.nInserted || 0), 0)
|
|
1417
|
+
|
|
1418
|
+
//10欄須全數保留且值為各自行程之tag
|
|
1419
|
+
let vSav = await woSav.selectByTime(tSav)
|
|
1420
|
+
vget[6] = Array.from({ length: 10 }, (v, k) => k)
|
|
1421
|
+
.filter((k) => {
|
|
1422
|
+
let tag = k <= 4 ? 'p1' : 'p2'
|
|
1423
|
+
return vSav[`f${k}`] === tag
|
|
1424
|
+
}).length
|
|
1425
|
+
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
vans[1] = 0
|
|
1429
|
+
it(`should get ${JSON.stringify(vans[1])} for count of errored process in cross-process insert`, async function() {
|
|
1430
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1431
|
+
})
|
|
1432
|
+
|
|
1433
|
+
vans[2] = 20
|
|
1434
|
+
it(`should get ${JSON.stringify(vans[2])} for sum of nInserted by 2 processes inserting same 20 times`, async function() {
|
|
1435
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1436
|
+
})
|
|
1437
|
+
|
|
1438
|
+
vans[3] = 20
|
|
1439
|
+
it(`should get ${JSON.stringify(vans[3])} for records after cross-process insert`, async function() {
|
|
1440
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1441
|
+
})
|
|
1442
|
+
|
|
1443
|
+
vans[4] = 0
|
|
1444
|
+
it(`should get ${JSON.stringify(vans[4])} for count of errored process in cross-process save`, async function() {
|
|
1445
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1446
|
+
})
|
|
1447
|
+
|
|
1448
|
+
vans[5] = 1
|
|
1449
|
+
it(`should get ${JSON.stringify(vans[5])} for sum of nInserted by 2 processes saving same new time`, async function() {
|
|
1450
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1451
|
+
})
|
|
1452
|
+
|
|
1453
|
+
vans[6] = 10
|
|
1454
|
+
it(`should get ${JSON.stringify(vans[6])} for count of fields kept after cross-process save with different field`, async function() {
|
|
1455
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
})
|