w-orm-postgresql 1.0.7 → 1.0.8
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 +167 -6
- package/docs/WOrmPostgresql.mjs.html +247 -62
- package/docs/index.html +2 -2
- package/g-basic.mjs +15 -1
- package/package.json +2 -2
- package/src/WOrmPostgresql.mjs +245 -60
- package/test/{basic.test.mjs → api-postgresql.test.mjs} +463 -21
|
@@ -1,7 +1,134 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import net from 'net'
|
|
3
|
+
import pg from 'pg'
|
|
4
|
+
import { execFile } from 'child_process'
|
|
5
|
+
import { promisify } from 'util'
|
|
2
6
|
import WOrm from '../src/WOrmPostgresql.mjs'
|
|
3
7
|
|
|
4
8
|
|
|
9
|
+
//測試自行創建docker容器供PostgreSQL服務, 測試結束即銷毀, 故僅需環境已安裝docker
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
let execFileAsync = promisify(execFile)
|
|
13
|
+
|
|
14
|
+
let ctName = 'worm-test-postgresql'
|
|
15
|
+
let ctImage = 'postgres:17-alpine'
|
|
16
|
+
let ctUser = 'username'
|
|
17
|
+
let ctPassword = 'password'
|
|
18
|
+
let ctDb = 'worm'
|
|
19
|
+
let ctPort = null //容器對外埠, 由getFreePort動態取得, 避免與既有PostgreSQL服務搶5432
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
let delay = (ms) => {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
setTimeout(resolve, ms)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//getFreePort, 由系統配發空閒埠
|
|
29
|
+
let getFreePort = () => {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let srv = net.createServer()
|
|
32
|
+
srv.on('error', reject)
|
|
33
|
+
srv.listen(0, () => {
|
|
34
|
+
let port = srv.address().port
|
|
35
|
+
srv.close(() => {
|
|
36
|
+
resolve(port)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//docker, 以陣列傳參不經shell, 避免路徑與引號轉譯問題
|
|
43
|
+
let docker = (args) => {
|
|
44
|
+
return execFileAsync('docker', args)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//genUrl
|
|
48
|
+
let genUrl = () => {
|
|
49
|
+
return `postgresql://${ctUser}:${ctPassword}@127.0.0.1:${ctPort}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//genOpt
|
|
53
|
+
let genOpt = (cl = 'users', useCache = false) => {
|
|
54
|
+
return {
|
|
55
|
+
url: genUrl(),
|
|
56
|
+
db: ctDb,
|
|
57
|
+
cl,
|
|
58
|
+
useCache,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//probeReady, 以pg直連容器對外埠試跑SELECT 1
|
|
63
|
+
//不使用容器內pg_isready, 因postgres映像初始化期間會先啟動僅監聽unix socket之臨時服務,
|
|
64
|
+
//pg_isready預設亦走unix socket故會提早回報就緒, 直連TCP方為測試實際所走路徑
|
|
65
|
+
let probeReady = async () => {
|
|
66
|
+
let client = new pg.Client({ connectionString: `${genUrl()}/${ctDb}` })
|
|
67
|
+
let ok = false
|
|
68
|
+
try {
|
|
69
|
+
await client.connect()
|
|
70
|
+
await client.query('SELECT 1')
|
|
71
|
+
ok = true
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
ok = false
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await client.end().catch(() => {})
|
|
78
|
+
}
|
|
79
|
+
return ok
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//startContainer, 創建容器並等待服務就緒
|
|
83
|
+
//postgres映像以POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB即建妥帳號與資料庫, 無須另跑建置語句
|
|
84
|
+
let startContainer = async () => {
|
|
85
|
+
|
|
86
|
+
//check docker
|
|
87
|
+
await docker(['version', '--format', '{{.Server.Version}}'])
|
|
88
|
+
.catch(() => {
|
|
89
|
+
throw new Error('需先安裝並啟動docker才能執行postgresql測試')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
//清除前次殘留容器
|
|
93
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
94
|
+
|
|
95
|
+
//run
|
|
96
|
+
ctPort = await getFreePort()
|
|
97
|
+
await docker(['run', '-d', '--rm', '--name', ctName, '-e', `POSTGRES_USER=${ctUser}`, '-e', `POSTGRES_PASSWORD=${ctPassword}`, '-e', `POSTGRES_DB=${ctDb}`, '-p', `${ctPort}:5432`, ctImage])
|
|
98
|
+
|
|
99
|
+
//等待服務就緒, 首次需拉取映像故給予較長時間
|
|
100
|
+
let ready = false
|
|
101
|
+
for (let i = 0; i < 90; i++) {
|
|
102
|
+
let ok = await probeReady()
|
|
103
|
+
if (ok) {
|
|
104
|
+
ready = true
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
await delay(2000)
|
|
108
|
+
}
|
|
109
|
+
if (!ready) {
|
|
110
|
+
throw new Error('postgresql容器啟動逾時')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//stopContainer, 銷毀容器
|
|
116
|
+
let stopContainer = async () => {
|
|
117
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
before(async function() {
|
|
122
|
+
this.timeout(600000) //含拉取映像與服務啟動
|
|
123
|
+
await startContainer()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
after(async function() {
|
|
127
|
+
this.timeout(120000)
|
|
128
|
+
await stopContainer()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
|
|
5
132
|
describe('basic', function() {
|
|
6
133
|
let rt = null
|
|
7
134
|
let vans = {}
|
|
@@ -9,27 +136,7 @@ describe('basic', function() {
|
|
|
9
136
|
|
|
10
137
|
before(async function () {
|
|
11
138
|
|
|
12
|
-
let opt =
|
|
13
|
-
url: 'postgresql://username:password@127.0.0.1:5432',
|
|
14
|
-
db: 'worm',
|
|
15
|
-
cl: 'users',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
//probe, 偵測postgres是否可用, 不可用則skip全部測試
|
|
19
|
-
//pg預設無connect timeout, 連不到時會等到OS socket timeout (~75s), 故用Promise.race包3s timeout
|
|
20
|
-
try {
|
|
21
|
-
let probe = WOrm(opt)
|
|
22
|
-
await Promise.race([
|
|
23
|
-
probe.select(),
|
|
24
|
-
new Promise((_resolve, reject) => setTimeout(() => reject(new Error('probe timeout')), 3000)),
|
|
25
|
-
])
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
let msg = err && err.message ? err.message : err
|
|
29
|
-
console.log(`PostgreSQL unavailable, skip tests: ${msg}`)
|
|
30
|
-
this.skip()
|
|
31
|
-
return
|
|
32
|
-
}
|
|
139
|
+
let opt = genOpt('users')
|
|
33
140
|
|
|
34
141
|
let rs = [
|
|
35
142
|
{
|
|
@@ -264,6 +371,51 @@ describe('basic', function() {
|
|
|
264
371
|
})
|
|
265
372
|
vget[6] = rt
|
|
266
373
|
|
|
374
|
+
//selectByTime
|
|
375
|
+
rt = null
|
|
376
|
+
// vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
377
|
+
await wo.selectByTime('2025-01-01T00:10:00Z')
|
|
378
|
+
.then(function(msg) {
|
|
379
|
+
// console.log('selectByTime then', msg)
|
|
380
|
+
// selectByTime { time: 2025-01-01T00:10:00.000Z, name: 'rosemary(modify)', value: 113.98 }
|
|
381
|
+
rt = msg
|
|
382
|
+
})
|
|
383
|
+
.catch(function(msg) {
|
|
384
|
+
// console.log('selectByTime catch', msg)
|
|
385
|
+
rt = msg.toString()
|
|
386
|
+
})
|
|
387
|
+
vget[18] = rt
|
|
388
|
+
|
|
389
|
+
//selectByTime by time not existed
|
|
390
|
+
rt = null
|
|
391
|
+
// vans[19] = null
|
|
392
|
+
await wo.selectByTime('2024-01-01T00:00:00Z')
|
|
393
|
+
.then(function(msg) {
|
|
394
|
+
// console.log('selectByTime by time not existed then', msg)
|
|
395
|
+
// selectByTime by time not existed null
|
|
396
|
+
rt = msg
|
|
397
|
+
})
|
|
398
|
+
.catch(function(msg) {
|
|
399
|
+
// console.log('selectByTime by time not existed catch', msg)
|
|
400
|
+
rt = msg.toString()
|
|
401
|
+
})
|
|
402
|
+
vget[19] = rt
|
|
403
|
+
|
|
404
|
+
//selectByTime by time invalid, 未給有效主鍵值視為查無數據
|
|
405
|
+
rt = null
|
|
406
|
+
// vans[20] = null
|
|
407
|
+
await wo.selectByTime('abc')
|
|
408
|
+
.then(function(msg) {
|
|
409
|
+
// console.log('selectByTime by time invalid then', msg)
|
|
410
|
+
// selectByTime by time invalid null
|
|
411
|
+
rt = msg
|
|
412
|
+
})
|
|
413
|
+
.catch(function(msg) {
|
|
414
|
+
// console.log('selectByTime by time invalid catch', msg)
|
|
415
|
+
rt = msg.toString()
|
|
416
|
+
})
|
|
417
|
+
vget[20] = rt
|
|
418
|
+
|
|
267
419
|
//select by $and, $gt, $lt
|
|
268
420
|
rt = null
|
|
269
421
|
// vans[7] = [
|
|
@@ -520,6 +672,34 @@ describe('basic', function() {
|
|
|
520
672
|
})
|
|
521
673
|
vget[17] = rt
|
|
522
674
|
|
|
675
|
+
//selectByTime (1st time, 從DB讀取並填入快取)
|
|
676
|
+
rt = null
|
|
677
|
+
// vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
678
|
+
await woc.selectByTime('2025-01-01T00:01:00Z')
|
|
679
|
+
.then(function(msg) {
|
|
680
|
+
// console.log('selectByTime 1st then', msg)
|
|
681
|
+
rt = msg
|
|
682
|
+
})
|
|
683
|
+
.catch(function(msg) {
|
|
684
|
+
// console.log('selectByTime 1st catch', msg)
|
|
685
|
+
rt = msg.toString()
|
|
686
|
+
})
|
|
687
|
+
vget[21] = rt
|
|
688
|
+
|
|
689
|
+
//selectByTime (2nd time, 命中快取, 內容須與第一次相同)
|
|
690
|
+
rt = null
|
|
691
|
+
// vans[22] = vans[21]
|
|
692
|
+
await woc.selectByTime('2025-01-01T00:01:00Z')
|
|
693
|
+
.then(function(msg) {
|
|
694
|
+
// console.log('selectByTime 2nd then', msg)
|
|
695
|
+
rt = msg
|
|
696
|
+
})
|
|
697
|
+
.catch(function(msg) {
|
|
698
|
+
// console.log('selectByTime 2nd catch', msg)
|
|
699
|
+
rt = msg.toString()
|
|
700
|
+
})
|
|
701
|
+
vget[22] = rt
|
|
702
|
+
|
|
523
703
|
})
|
|
524
704
|
|
|
525
705
|
vans[1] = { ok: 1 }
|
|
@@ -576,6 +756,22 @@ describe('basic', function() {
|
|
|
576
756
|
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
577
757
|
})
|
|
578
758
|
|
|
759
|
+
//vans[18]~vans[22]為後續新增之selectByTime, 為不更動既有編號故接續於末號之後
|
|
760
|
+
vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
761
|
+
it(`should get ${JSON.stringify(vans[18])} for selectByTime`, async function() {
|
|
762
|
+
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
vans[19] = null
|
|
766
|
+
it(`should get ${JSON.stringify(vans[19])} for selectByTime by time not existed`, async function() {
|
|
767
|
+
assert.strict.deepStrictEqual(vget[19], vans[19])
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
vans[20] = null
|
|
771
|
+
it(`should get ${JSON.stringify(vans[20])} for selectByTime by time invalid`, async function() {
|
|
772
|
+
assert.strict.deepStrictEqual(vget[20], vans[20])
|
|
773
|
+
})
|
|
774
|
+
|
|
579
775
|
vans[7] = [
|
|
580
776
|
{ time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary', value: 123.456 },
|
|
581
777
|
{ time: new Date('2025-01-01T00:04:00.000Z'), name: 'rosemary', value: 123.1236 },
|
|
@@ -678,4 +874,250 @@ describe('basic', function() {
|
|
|
678
874
|
assert.strict.deepStrictEqual(vget[17], vans[17])
|
|
679
875
|
})
|
|
680
876
|
|
|
877
|
+
vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
878
|
+
it(`should get ${JSON.stringify(vans[21])} for selectByTime (1st, fill cache)`, async function() {
|
|
879
|
+
assert.strict.deepStrictEqual(vget[21], vans[21])
|
|
880
|
+
})
|
|
881
|
+
|
|
882
|
+
vans[22] = vans[21]
|
|
883
|
+
it(`should get ${JSON.stringify(vans[22])} for selectByTime (2nd, cache hit)`, async function() {
|
|
884
|
+
assert.strict.deepStrictEqual(vget[22], vans[22])
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
//insert與save之併發測試, 因各操作皆各自new PgClient另開連線, 原子性全由PostgreSQL提供,
|
|
891
|
+
//故同行程以Promise.all併發即等價於跨行程併發, 不須另起行程
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
describe('insert nInserted', function() {
|
|
895
|
+
let vans = {}
|
|
896
|
+
let vget = {}
|
|
897
|
+
|
|
898
|
+
before(async function() {
|
|
899
|
+
this.timeout(120000)
|
|
900
|
+
|
|
901
|
+
//wo
|
|
902
|
+
let cl = 'insertcnt'
|
|
903
|
+
let wo = WOrm(genOpt(cl))
|
|
904
|
+
|
|
905
|
+
//createTable
|
|
906
|
+
await wo.createTable(cl, 'time', {
|
|
907
|
+
time: '2000-01-01T00:00:00Z',
|
|
908
|
+
name: 'abc',
|
|
909
|
+
value: 0.1,
|
|
910
|
+
}).catch(() => {})
|
|
911
|
+
await wo.delAll()
|
|
912
|
+
|
|
913
|
+
//全新3筆
|
|
914
|
+
vget[1] = await wo.insert([
|
|
915
|
+
{ time: '2025-02-01T00:00:00Z', name: 'peter', value: 1 },
|
|
916
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
917
|
+
{ time: '2025-02-01T00:02:00Z', name: 'kettle', value: 3 },
|
|
918
|
+
])
|
|
919
|
+
|
|
920
|
+
//重複插入同3筆, 全數已存在故皆不插入
|
|
921
|
+
vget[2] = await wo.insert([
|
|
922
|
+
{ time: '2025-02-01T00:00:00Z', name: 'peter', value: 1 },
|
|
923
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
924
|
+
{ time: '2025-02-01T00:02:00Z', name: 'kettle', value: 3 },
|
|
925
|
+
])
|
|
926
|
+
|
|
927
|
+
//部份重複, 1筆已存在2筆為新
|
|
928
|
+
vget[3] = await wo.insert([
|
|
929
|
+
{ time: '2025-02-01T00:01:00Z', name: 'rosemary', value: 2 },
|
|
930
|
+
{ time: '2025-02-01T00:03:00Z', name: 'sandler', value: 4 },
|
|
931
|
+
{ time: '2025-02-01T00:04:00Z', name: 'joyce', value: 5 },
|
|
932
|
+
])
|
|
933
|
+
|
|
934
|
+
//同批含重複time, 僅首筆成功
|
|
935
|
+
vget[4] = await wo.insert([
|
|
936
|
+
{ time: '2025-02-01T00:05:00Z', name: 'dup-1', value: 6 },
|
|
937
|
+
{ time: '2025-02-01T00:05:00Z', name: 'dup-2', value: 7 },
|
|
938
|
+
{ time: '2025-02-01T00:06:00Z', name: 'uniq', value: 8 },
|
|
939
|
+
])
|
|
940
|
+
|
|
941
|
+
//併發對同一time插入10次, nInserted總和須為1
|
|
942
|
+
let rs = await Promise.all(Array.from({ length: 10 }, (v, k) => {
|
|
943
|
+
return wo.insert({ time: '2025-02-01T00:07:00Z', name: `race-${k}`, value: k })
|
|
944
|
+
}))
|
|
945
|
+
vget[5] = rs.reduce((sum, v) => sum + v.nInserted, 0)
|
|
946
|
+
|
|
947
|
+
//併發皆不得報錯
|
|
948
|
+
vget[6] = rs.filter((v) => v.ok !== 1).length
|
|
949
|
+
|
|
950
|
+
//最終筆數, 3+0+2+2+1=8
|
|
951
|
+
let ss = await wo.select()
|
|
952
|
+
vget[7] = ss.length
|
|
953
|
+
|
|
954
|
+
//同批重複者僅首筆入庫
|
|
955
|
+
let vdup = await wo.selectByTime('2025-02-01T00:05:00Z')
|
|
956
|
+
vget[8] = vdup.name
|
|
957
|
+
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
vans[1] = { n: 3, nInserted: 3, ok: 1 }
|
|
961
|
+
it(`should get ${JSON.stringify(vans[1])} for insert 3 new records`, async function() {
|
|
962
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
vans[2] = { n: 3, nInserted: 0, ok: 1 }
|
|
966
|
+
it(`should get ${JSON.stringify(vans[2])} for insert 3 existed records`, async function() {
|
|
967
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
vans[3] = { n: 3, nInserted: 2, ok: 1 }
|
|
971
|
+
it(`should get ${JSON.stringify(vans[3])} for insert 1 existed and 2 new records`, async function() {
|
|
972
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
973
|
+
})
|
|
974
|
+
|
|
975
|
+
vans[4] = { n: 3, nInserted: 2, ok: 1 }
|
|
976
|
+
it(`should get ${JSON.stringify(vans[4])} for insert with duplicated time in same batch`, async function() {
|
|
977
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
vans[5] = 1
|
|
981
|
+
it(`should get ${JSON.stringify(vans[5])} for sum of nInserted by 10 concurrent insert with same time`, async function() {
|
|
982
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
vans[6] = 0
|
|
986
|
+
it(`should get ${JSON.stringify(vans[6])} for count of ok!==1 in 10 concurrent insert`, async function() {
|
|
987
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
vans[7] = 8
|
|
991
|
+
it(`should get ${JSON.stringify(vans[7])} for records after all insert`, async function() {
|
|
992
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
vans[8] = 'dup-1'
|
|
996
|
+
it(`should get ${JSON.stringify(vans[8])} for keeping first one of duplicated time in same batch`, async function() {
|
|
997
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
998
|
+
})
|
|
999
|
+
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
describe('save concurrency', function() {
|
|
1004
|
+
let vans = {}
|
|
1005
|
+
let vget = {}
|
|
1006
|
+
|
|
1007
|
+
before(async function() {
|
|
1008
|
+
this.timeout(120000)
|
|
1009
|
+
|
|
1010
|
+
//wo
|
|
1011
|
+
let cl = 'saverace'
|
|
1012
|
+
let wo = WOrm(genOpt(cl))
|
|
1013
|
+
|
|
1014
|
+
//createTable
|
|
1015
|
+
await wo.createTable(cl, 'time', {
|
|
1016
|
+
time: '2000-01-01T00:00:00Z',
|
|
1017
|
+
name: 'abc',
|
|
1018
|
+
value: 0.1,
|
|
1019
|
+
}).catch(() => {})
|
|
1020
|
+
await wo.delAll()
|
|
1021
|
+
|
|
1022
|
+
//併發save對既有time之不同欄位, 各欄位皆須保留
|
|
1023
|
+
let tMerge = '2025-03-01T00:00:00Z'
|
|
1024
|
+
await wo.insert({ time: tMerge, name: 'origin', value: 1 })
|
|
1025
|
+
await Promise.all([
|
|
1026
|
+
wo.save({ time: tMerge, name: 'merge-name' }),
|
|
1027
|
+
wo.save({ time: tMerge, value: 99 }),
|
|
1028
|
+
])
|
|
1029
|
+
vget[1] = await wo.selectByTime(tMerge)
|
|
1030
|
+
|
|
1031
|
+
//併發save對全新time, autoInsert僅一次且不得報錯
|
|
1032
|
+
let tNew = '2025-03-01T00:01:00Z'
|
|
1033
|
+
let rsn = await Promise.all(Array.from({ length: 5 }, (v, k) => {
|
|
1034
|
+
return wo.save({ time: tNew, name: `new-${k}` })
|
|
1035
|
+
}))
|
|
1036
|
+
vget[2] = rsn.filter((v) => v[0].nInserted === 1).length
|
|
1037
|
+
vget[3] = rsn.filter((v) => v[0].ok !== 1).length
|
|
1038
|
+
vget[4] = (await wo.select({ time: tNew })).length
|
|
1039
|
+
|
|
1040
|
+
//save內容相同不更新
|
|
1041
|
+
let tSame = '2025-03-01T00:02:00Z'
|
|
1042
|
+
await wo.insert({ time: tSame, name: 'same', value: 5 })
|
|
1043
|
+
vget[5] = await wo.save({ time: tSame, name: 'same', value: 5 })
|
|
1044
|
+
|
|
1045
|
+
//save內容不同須更新, 未給之欄位須保留
|
|
1046
|
+
let tDiff = '2025-03-01T00:03:00Z'
|
|
1047
|
+
await wo.insert({ time: tDiff, name: 'diff', value: 5 })
|
|
1048
|
+
vget[6] = await wo.save({ time: tDiff, value: 6 })
|
|
1049
|
+
vget[7] = await wo.selectByTime(tDiff)
|
|
1050
|
+
|
|
1051
|
+
//save(autoInsert=false)對不存在之time不得插入
|
|
1052
|
+
let tNone = '2025-03-01T00:04:00Z'
|
|
1053
|
+
vget[8] = await wo.save({ time: tNone, name: 'none' }, { autoInsert: false })
|
|
1054
|
+
vget[9] = await wo.selectByTime(tNone)
|
|
1055
|
+
|
|
1056
|
+
//併發save(autoInsert=false)對既有time之不同欄位, 各欄位皆須保留且不得報錯
|
|
1057
|
+
let tUpd = '2025-03-01T00:05:00Z'
|
|
1058
|
+
await wo.insert({ time: tUpd, name: 'origin', value: 1 })
|
|
1059
|
+
let rsu = await Promise.all([
|
|
1060
|
+
wo.save({ time: tUpd, name: 'upd-name' }, { autoInsert: false }),
|
|
1061
|
+
wo.save({ time: tUpd, value: 88 }, { autoInsert: false }),
|
|
1062
|
+
])
|
|
1063
|
+
vget[10] = rsu.filter((v) => v[0].ok !== 1).length
|
|
1064
|
+
vget[11] = await wo.selectByTime(tUpd)
|
|
1065
|
+
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
vans[1] = { time: new Date('2025-03-01T00:00:00.000Z'), name: 'merge-name', value: 99 }
|
|
1069
|
+
it(`should get ${JSON.stringify(vans[1])} for fields after 2 concurrent save with different field`, async function() {
|
|
1070
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
vans[2] = 1
|
|
1074
|
+
it(`should get ${JSON.stringify(vans[2])} for count of nInserted===1 in 5 concurrent save with time not existed`, async function() {
|
|
1075
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1076
|
+
})
|
|
1077
|
+
|
|
1078
|
+
vans[3] = 0
|
|
1079
|
+
it(`should get ${JSON.stringify(vans[3])} for count of ok!==1 in 5 concurrent save with time not existed`, async function() {
|
|
1080
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
vans[4] = 1
|
|
1084
|
+
it(`should get ${JSON.stringify(vans[4])} for records after 5 concurrent save with time not existed`, async function() {
|
|
1085
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1086
|
+
})
|
|
1087
|
+
|
|
1088
|
+
vans[5] = [{ n: 1, nModified: 0, ok: 1 }]
|
|
1089
|
+
it(`should get ${JSON.stringify(vans[5])} for save with same content`, async function() {
|
|
1090
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1091
|
+
})
|
|
1092
|
+
|
|
1093
|
+
vans[6] = [{ n: 1, nModified: 1, ok: 1 }]
|
|
1094
|
+
it(`should get ${JSON.stringify(vans[6])} for save with different content`, async function() {
|
|
1095
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
vans[7] = { time: new Date('2025-03-01T00:03:00.000Z'), name: 'diff', value: 6 }
|
|
1099
|
+
it(`should get ${JSON.stringify(vans[7])} for keeping field not given in save`, async function() {
|
|
1100
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
1101
|
+
})
|
|
1102
|
+
|
|
1103
|
+
vans[8] = [{ n: 0, nModified: 0, ok: 1 }]
|
|
1104
|
+
it(`should get ${JSON.stringify(vans[8])} for save(autoInsert=false) with time not existed`, async function() {
|
|
1105
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1106
|
+
})
|
|
1107
|
+
|
|
1108
|
+
vans[9] = null
|
|
1109
|
+
it(`should get ${JSON.stringify(vans[9])} for record after save(autoInsert=false) with time not existed`, async function() {
|
|
1110
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
1111
|
+
})
|
|
1112
|
+
|
|
1113
|
+
vans[10] = 0
|
|
1114
|
+
it(`should get ${JSON.stringify(vans[10])} for count of ok!==1 in 2 concurrent save(autoInsert=false)`, async function() {
|
|
1115
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
vans[11] = { time: new Date('2025-03-01T00:05:00.000Z'), name: 'upd-name', value: 88 }
|
|
1119
|
+
it(`should get ${JSON.stringify(vans[11])} for fields after 2 concurrent save(autoInsert=false) with different field`, async function() {
|
|
1120
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
1121
|
+
})
|
|
1122
|
+
|
|
681
1123
|
})
|