w-orm-postgresql 1.0.8 → 1.0.10
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 +16 -16
- package/dist/w-orm-postgresql.umd.js +2 -2
- package/dist/w-orm-postgresql.umd.js.map +1 -1
- package/docs/WOrmPostgresql.html +138 -27
- package/docs/WOrmPostgresql.mjs.html +148 -56
- package/docs/index.html +2 -2
- package/g-basic.mjs +16 -16
- package/package.json +1 -1
- package/src/WOrmPostgresql.mjs +146 -54
- package/test/api-postgresql.test.mjs +513 -46
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
2
|
import net from 'net'
|
|
3
|
+
import path from 'path'
|
|
3
4
|
import pg from 'pg'
|
|
4
|
-
import { execFile } from 'child_process'
|
|
5
|
+
import { execFile, spawn } from 'child_process'
|
|
5
6
|
import { promisify } from 'util'
|
|
7
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
6
8
|
import WOrm from '../src/WOrmPostgresql.mjs'
|
|
7
9
|
|
|
8
10
|
|
|
@@ -371,47 +373,47 @@ describe('basic', function() {
|
|
|
371
373
|
})
|
|
372
374
|
vget[6] = rt
|
|
373
375
|
|
|
374
|
-
//
|
|
376
|
+
//selectByPk
|
|
375
377
|
rt = null
|
|
376
378
|
// vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
377
|
-
await wo.
|
|
379
|
+
await wo.selectByPk('2025-01-01T00:10:00Z')
|
|
378
380
|
.then(function(msg) {
|
|
379
|
-
// console.log('
|
|
380
|
-
//
|
|
381
|
+
// console.log('selectByPk then', msg)
|
|
382
|
+
// selectByPk { time: 2025-01-01T00:10:00.000Z, name: 'rosemary(modify)', value: 113.98 }
|
|
381
383
|
rt = msg
|
|
382
384
|
})
|
|
383
385
|
.catch(function(msg) {
|
|
384
|
-
// console.log('
|
|
386
|
+
// console.log('selectByPk catch', msg)
|
|
385
387
|
rt = msg.toString()
|
|
386
388
|
})
|
|
387
389
|
vget[18] = rt
|
|
388
390
|
|
|
389
|
-
//
|
|
391
|
+
//selectByPk by pk not existed
|
|
390
392
|
rt = null
|
|
391
393
|
// vans[19] = null
|
|
392
|
-
await wo.
|
|
394
|
+
await wo.selectByPk('2024-01-01T00:00:00Z')
|
|
393
395
|
.then(function(msg) {
|
|
394
|
-
// console.log('
|
|
395
|
-
//
|
|
396
|
+
// console.log('selectByPk by pk not existed then', msg)
|
|
397
|
+
// selectByPk by pk not existed null
|
|
396
398
|
rt = msg
|
|
397
399
|
})
|
|
398
400
|
.catch(function(msg) {
|
|
399
|
-
// console.log('
|
|
401
|
+
// console.log('selectByPk by pk not existed catch', msg)
|
|
400
402
|
rt = msg.toString()
|
|
401
403
|
})
|
|
402
404
|
vget[19] = rt
|
|
403
405
|
|
|
404
|
-
//
|
|
406
|
+
//selectByPk by pk invalid, 未給有效主鍵值視為查無數據
|
|
405
407
|
rt = null
|
|
406
408
|
// vans[20] = null
|
|
407
|
-
await wo.
|
|
409
|
+
await wo.selectByPk('abc')
|
|
408
410
|
.then(function(msg) {
|
|
409
|
-
// console.log('
|
|
410
|
-
//
|
|
411
|
+
// console.log('selectByPk by pk invalid then', msg)
|
|
412
|
+
// selectByPk by pk invalid null
|
|
411
413
|
rt = msg
|
|
412
414
|
})
|
|
413
415
|
.catch(function(msg) {
|
|
414
|
-
// console.log('
|
|
416
|
+
// console.log('selectByPk by pk invalid catch', msg)
|
|
415
417
|
rt = msg.toString()
|
|
416
418
|
})
|
|
417
419
|
vget[20] = rt
|
|
@@ -672,30 +674,30 @@ describe('basic', function() {
|
|
|
672
674
|
})
|
|
673
675
|
vget[17] = rt
|
|
674
676
|
|
|
675
|
-
//
|
|
677
|
+
//selectByPk (1st time, 從DB讀取並填入快取)
|
|
676
678
|
rt = null
|
|
677
679
|
// vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
678
|
-
await woc.
|
|
680
|
+
await woc.selectByPk('2025-01-01T00:01:00Z')
|
|
679
681
|
.then(function(msg) {
|
|
680
|
-
// console.log('
|
|
682
|
+
// console.log('selectByPk 1st then', msg)
|
|
681
683
|
rt = msg
|
|
682
684
|
})
|
|
683
685
|
.catch(function(msg) {
|
|
684
|
-
// console.log('
|
|
686
|
+
// console.log('selectByPk 1st catch', msg)
|
|
685
687
|
rt = msg.toString()
|
|
686
688
|
})
|
|
687
689
|
vget[21] = rt
|
|
688
690
|
|
|
689
|
-
//
|
|
691
|
+
//selectByPk (2nd time, 命中快取, 內容須與第一次相同)
|
|
690
692
|
rt = null
|
|
691
693
|
// vans[22] = vans[21]
|
|
692
|
-
await woc.
|
|
694
|
+
await woc.selectByPk('2025-01-01T00:01:00Z')
|
|
693
695
|
.then(function(msg) {
|
|
694
|
-
// console.log('
|
|
696
|
+
// console.log('selectByPk 2nd then', msg)
|
|
695
697
|
rt = msg
|
|
696
698
|
})
|
|
697
699
|
.catch(function(msg) {
|
|
698
|
-
// console.log('
|
|
700
|
+
// console.log('selectByPk 2nd catch', msg)
|
|
699
701
|
rt = msg.toString()
|
|
700
702
|
})
|
|
701
703
|
vget[22] = rt
|
|
@@ -713,11 +715,11 @@ describe('basic', function() {
|
|
|
713
715
|
})
|
|
714
716
|
|
|
715
717
|
vans[3] = [
|
|
716
|
-
{ n: 1, nModified: 0, ok: 1 },
|
|
717
|
-
{ n: 1, nModified: 1, ok: 1 },
|
|
718
|
-
{ n: 1, nModified: 1, ok: 1 },
|
|
719
|
-
{ n: 1, nModified: 1, ok: 1 },
|
|
720
|
-
{ n: 0, nModified: 0, ok: 1 }
|
|
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 }
|
|
721
723
|
]
|
|
722
724
|
it(`should get ${JSON.stringify(vans[3])} for save(autoInsert=false)`, async function() {
|
|
723
725
|
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
@@ -756,19 +758,19 @@ describe('basic', function() {
|
|
|
756
758
|
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
757
759
|
})
|
|
758
760
|
|
|
759
|
-
//vans[18]~vans[22]為後續新增之
|
|
761
|
+
//vans[18]~vans[22]為後續新增之selectByPk, 為不更動既有編號故接續於末號之後
|
|
760
762
|
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
|
|
763
|
+
it(`should get ${JSON.stringify(vans[18])} for selectByPk`, async function() {
|
|
762
764
|
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
763
765
|
})
|
|
764
766
|
|
|
765
767
|
vans[19] = null
|
|
766
|
-
it(`should get ${JSON.stringify(vans[19])} for
|
|
768
|
+
it(`should get ${JSON.stringify(vans[19])} for selectByPk by pk not existed`, async function() {
|
|
767
769
|
assert.strict.deepStrictEqual(vget[19], vans[19])
|
|
768
770
|
})
|
|
769
771
|
|
|
770
772
|
vans[20] = null
|
|
771
|
-
it(`should get ${JSON.stringify(vans[20])} for
|
|
773
|
+
it(`should get ${JSON.stringify(vans[20])} for selectByPk by pk invalid`, async function() {
|
|
772
774
|
assert.strict.deepStrictEqual(vget[20], vans[20])
|
|
773
775
|
})
|
|
774
776
|
|
|
@@ -810,12 +812,12 @@ describe('basic', function() {
|
|
|
810
812
|
// assert.strict.deepStrictEqual(vget[14], vans[14])
|
|
811
813
|
// })
|
|
812
814
|
|
|
813
|
-
vans[10] = [{ n: 1, nInserted: 1, ok: 1 }]
|
|
815
|
+
vans[10] = [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }]
|
|
814
816
|
it(`should get ${JSON.stringify(vans[10])} for save(autoInsert=true)`, async function() {
|
|
815
817
|
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
816
818
|
})
|
|
817
819
|
|
|
818
|
-
vans[11] = [{ n:
|
|
820
|
+
vans[11] = [{ n: 0, nDeleted: 0, ok: 1 }]
|
|
819
821
|
it(`should get ${JSON.stringify(vans[11])} for del`, async function() {
|
|
820
822
|
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
821
823
|
})
|
|
@@ -860,7 +862,7 @@ describe('basic', function() {
|
|
|
860
862
|
assert.strict.deepStrictEqual(vget[15], vans[15])
|
|
861
863
|
})
|
|
862
864
|
|
|
863
|
-
vans[16] = [{ n: 1, nModified: 1, ok: 1 }]
|
|
865
|
+
vans[16] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
|
|
864
866
|
it(`should get ${JSON.stringify(vans[16])} for save (invalidate cache)`, async function() {
|
|
865
867
|
assert.strict.deepStrictEqual(vget[16], vans[16])
|
|
866
868
|
})
|
|
@@ -875,12 +877,12 @@ describe('basic', function() {
|
|
|
875
877
|
})
|
|
876
878
|
|
|
877
879
|
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
|
|
880
|
+
it(`should get ${JSON.stringify(vans[21])} for selectByPk (1st, fill cache)`, async function() {
|
|
879
881
|
assert.strict.deepStrictEqual(vget[21], vans[21])
|
|
880
882
|
})
|
|
881
883
|
|
|
882
884
|
vans[22] = vans[21]
|
|
883
|
-
it(`should get ${JSON.stringify(vans[22])} for
|
|
885
|
+
it(`should get ${JSON.stringify(vans[22])} for selectByPk (2nd, cache hit)`, async function() {
|
|
884
886
|
assert.strict.deepStrictEqual(vget[22], vans[22])
|
|
885
887
|
})
|
|
886
888
|
|
|
@@ -952,7 +954,7 @@ describe('insert nInserted', function() {
|
|
|
952
954
|
vget[7] = ss.length
|
|
953
955
|
|
|
954
956
|
//同批重複者僅首筆入庫
|
|
955
|
-
let vdup = await wo.
|
|
957
|
+
let vdup = await wo.selectByPk('2025-02-01T00:05:00Z')
|
|
956
958
|
vget[8] = vdup.name
|
|
957
959
|
|
|
958
960
|
})
|
|
@@ -1026,7 +1028,7 @@ describe('save concurrency', function() {
|
|
|
1026
1028
|
wo.save({ time: tMerge, name: 'merge-name' }),
|
|
1027
1029
|
wo.save({ time: tMerge, value: 99 }),
|
|
1028
1030
|
])
|
|
1029
|
-
vget[1] = await wo.
|
|
1031
|
+
vget[1] = await wo.selectByPk(tMerge)
|
|
1030
1032
|
|
|
1031
1033
|
//併發save對全新time, autoInsert僅一次且不得報錯
|
|
1032
1034
|
let tNew = '2025-03-01T00:01:00Z'
|
|
@@ -1046,12 +1048,12 @@ describe('save concurrency', function() {
|
|
|
1046
1048
|
let tDiff = '2025-03-01T00:03:00Z'
|
|
1047
1049
|
await wo.insert({ time: tDiff, name: 'diff', value: 5 })
|
|
1048
1050
|
vget[6] = await wo.save({ time: tDiff, value: 6 })
|
|
1049
|
-
vget[7] = await wo.
|
|
1051
|
+
vget[7] = await wo.selectByPk(tDiff)
|
|
1050
1052
|
|
|
1051
1053
|
//save(autoInsert=false)對不存在之time不得插入
|
|
1052
1054
|
let tNone = '2025-03-01T00:04:00Z'
|
|
1053
1055
|
vget[8] = await wo.save({ time: tNone, name: 'none' }, { autoInsert: false })
|
|
1054
|
-
vget[9] = await wo.
|
|
1056
|
+
vget[9] = await wo.selectByPk(tNone)
|
|
1055
1057
|
|
|
1056
1058
|
//併發save(autoInsert=false)對既有time之不同欄位, 各欄位皆須保留且不得報錯
|
|
1057
1059
|
let tUpd = '2025-03-01T00:05:00Z'
|
|
@@ -1061,7 +1063,7 @@ describe('save concurrency', function() {
|
|
|
1061
1063
|
wo.save({ time: tUpd, value: 88 }, { autoInsert: false }),
|
|
1062
1064
|
])
|
|
1063
1065
|
vget[10] = rsu.filter((v) => v[0].ok !== 1).length
|
|
1064
|
-
vget[11] = await wo.
|
|
1066
|
+
vget[11] = await wo.selectByPk(tUpd)
|
|
1065
1067
|
|
|
1066
1068
|
})
|
|
1067
1069
|
|
|
@@ -1085,12 +1087,12 @@ describe('save concurrency', function() {
|
|
|
1085
1087
|
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1086
1088
|
})
|
|
1087
1089
|
|
|
1088
|
-
vans[5] = [{ n: 1, nModified: 0, ok: 1 }]
|
|
1090
|
+
vans[5] = [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1089
1091
|
it(`should get ${JSON.stringify(vans[5])} for save with same content`, async function() {
|
|
1090
1092
|
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1091
1093
|
})
|
|
1092
1094
|
|
|
1093
|
-
vans[6] = [{ n: 1, nModified: 1, ok: 1 }]
|
|
1095
|
+
vans[6] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
|
|
1094
1096
|
it(`should get ${JSON.stringify(vans[6])} for save with different content`, async function() {
|
|
1095
1097
|
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1096
1098
|
})
|
|
@@ -1100,7 +1102,7 @@ describe('save concurrency', function() {
|
|
|
1100
1102
|
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
1101
1103
|
})
|
|
1102
1104
|
|
|
1103
|
-
vans[8] = [{ n: 0, nModified: 0, ok: 1 }]
|
|
1105
|
+
vans[8] = [{ n: 0, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1104
1106
|
it(`should get ${JSON.stringify(vans[8])} for save(autoInsert=false) with time not existed`, async function() {
|
|
1105
1107
|
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1106
1108
|
})
|
|
@@ -1121,3 +1123,468 @@ describe('save concurrency', function() {
|
|
|
1121
1123
|
})
|
|
1122
1124
|
|
|
1123
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.selectByPk(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
|
+
describe('custom pk', function() {
|
|
1306
|
+
let vans = {}
|
|
1307
|
+
let vget = {}
|
|
1308
|
+
|
|
1309
|
+
before(async function() {
|
|
1310
|
+
this.timeout(120000)
|
|
1311
|
+
|
|
1312
|
+
//wo, 以opt.pk指定主鍵欄位為code, 驗證select以外之五函數皆改以該欄位認定主鍵
|
|
1313
|
+
let cl = 'custompk'
|
|
1314
|
+
let wo = WOrm({
|
|
1315
|
+
...genOpt(cl),
|
|
1316
|
+
pk: 'code',
|
|
1317
|
+
})
|
|
1318
|
+
|
|
1319
|
+
//createTable, pk留空以驗證採用建構時之opt.pk
|
|
1320
|
+
await wo.createTable(cl, null, {
|
|
1321
|
+
code: 'abc',
|
|
1322
|
+
name: 'abc',
|
|
1323
|
+
value: 0.1,
|
|
1324
|
+
}).catch(() => {})
|
|
1325
|
+
await wo.delAll()
|
|
1326
|
+
|
|
1327
|
+
//insert以code認定主鍵
|
|
1328
|
+
vget[1] = await wo.insert([
|
|
1329
|
+
{ code: 'c1', name: 'peter', value: 1 },
|
|
1330
|
+
{ code: 'c2', name: 'rosemary', value: 2 },
|
|
1331
|
+
])
|
|
1332
|
+
|
|
1333
|
+
//insert同code者跳過, 證明ON CONFLICT係以code為衝突目標
|
|
1334
|
+
vget[2] = await wo.insert({ code: 'c1', name: 'other', value: 9 })
|
|
1335
|
+
|
|
1336
|
+
//selectByPk以code查找
|
|
1337
|
+
vget[3] = await wo.selectByPk('c1')
|
|
1338
|
+
|
|
1339
|
+
//selectByPk未命中
|
|
1340
|
+
vget[4] = await wo.selectByPk('notexist')
|
|
1341
|
+
|
|
1342
|
+
//save只給部份欄位且值不同, 未給之欄位須保留
|
|
1343
|
+
vget[5] = await wo.save({ code: 'c1', value: 5 })
|
|
1344
|
+
vget[6] = await wo.selectByPk('c1')
|
|
1345
|
+
|
|
1346
|
+
//save合併後內容相同不更新
|
|
1347
|
+
vget[7] = await wo.save({ code: 'c1', value: 5 })
|
|
1348
|
+
|
|
1349
|
+
//save對不存在之code自動插入
|
|
1350
|
+
vget[8] = await wo.save({ code: 'c3', name: 'sandler' })
|
|
1351
|
+
|
|
1352
|
+
//del以code刪除
|
|
1353
|
+
vget[9] = await wo.del({ code: 'c2' })
|
|
1354
|
+
|
|
1355
|
+
//del未帶有效code
|
|
1356
|
+
let rsd = await wo.del({ name: 'no-code' })
|
|
1357
|
+
vget[10] = {
|
|
1358
|
+
n: rsd[0].n,
|
|
1359
|
+
nDeleted: rsd[0].nDeleted,
|
|
1360
|
+
ok: rsd[0].ok,
|
|
1361
|
+
hasErr: isestr(rsd[0].err),
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
//insert未帶有效code須reject
|
|
1365
|
+
vget[11] = await wo.insert({ name: 'no-code' })
|
|
1366
|
+
.then(() => 'resolved')
|
|
1367
|
+
.catch(() => 'rejected')
|
|
1368
|
+
|
|
1369
|
+
//最終筆數, 僅存c1與c3
|
|
1370
|
+
vget[12] = (await wo.select()).length
|
|
1371
|
+
|
|
1372
|
+
})
|
|
1373
|
+
|
|
1374
|
+
vans[1] = { n: 2, nInserted: 2, ok: 1 }
|
|
1375
|
+
it(`should get ${JSON.stringify(vans[1])} for insert with custom pk`, async function() {
|
|
1376
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1377
|
+
})
|
|
1378
|
+
|
|
1379
|
+
vans[2] = { n: 1, nInserted: 0, ok: 1 }
|
|
1380
|
+
it(`should get ${JSON.stringify(vans[2])} for insert with existed custom pk`, async function() {
|
|
1381
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1382
|
+
})
|
|
1383
|
+
|
|
1384
|
+
vans[3] = { code: 'c1', name: 'peter', value: 1 }
|
|
1385
|
+
it(`should get ${JSON.stringify(vans[3])} for selectByPk with custom pk`, async function() {
|
|
1386
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1387
|
+
})
|
|
1388
|
+
|
|
1389
|
+
vans[4] = null
|
|
1390
|
+
it(`should get ${JSON.stringify(vans[4])} for selectByPk by custom pk not existed`, async function() {
|
|
1391
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1392
|
+
})
|
|
1393
|
+
|
|
1394
|
+
vans[5] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
|
|
1395
|
+
it(`should get ${JSON.stringify(vans[5])} for save with custom pk`, async function() {
|
|
1396
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1397
|
+
})
|
|
1398
|
+
|
|
1399
|
+
vans[6] = { code: 'c1', name: 'peter', value: 5 }
|
|
1400
|
+
it(`should get ${JSON.stringify(vans[6])} for keeping field not given in save with custom pk`, async function() {
|
|
1401
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1402
|
+
})
|
|
1403
|
+
|
|
1404
|
+
vans[7] = [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }]
|
|
1405
|
+
it(`should get ${JSON.stringify(vans[7])} for save with same content by custom pk`, async function() {
|
|
1406
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
1407
|
+
})
|
|
1408
|
+
|
|
1409
|
+
vans[8] = [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }]
|
|
1410
|
+
it(`should get ${JSON.stringify(vans[8])} for save(autoInsert) with custom pk not existed`, async function() {
|
|
1411
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
vans[9] = [{ n: 1, nDeleted: 1, ok: 1 }]
|
|
1415
|
+
it(`should get ${JSON.stringify(vans[9])} for del with custom pk`, async function() {
|
|
1416
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
1417
|
+
})
|
|
1418
|
+
|
|
1419
|
+
vans[10] = { n: 0, nDeleted: 0, ok: 0, hasErr: true }
|
|
1420
|
+
it(`should get ${JSON.stringify(vans[10])} for del without valid custom pk`, async function() {
|
|
1421
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
1422
|
+
})
|
|
1423
|
+
|
|
1424
|
+
vans[11] = 'rejected'
|
|
1425
|
+
it(`should get ${JSON.stringify(vans[11])} for insert without valid custom pk`, async function() {
|
|
1426
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
1427
|
+
})
|
|
1428
|
+
|
|
1429
|
+
vans[12] = 2
|
|
1430
|
+
it(`should get ${JSON.stringify(vans[12])} for records after all operations with custom pk`, async function() {
|
|
1431
|
+
assert.strict.deepStrictEqual(vget[12], vans[12])
|
|
1432
|
+
})
|
|
1433
|
+
|
|
1434
|
+
})
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
//runProc, 另起行程對同一資料表操作, 用於驗證跨行程之原子性
|
|
1438
|
+
//子行程以--input-type=module執行, 並以pathToFileURL轉換src路徑, 避免專案路徑含非ASCII字元而無法import
|
|
1439
|
+
function runProc(tag, opt, mode, payload) {
|
|
1440
|
+
return new Promise(function(resolve) {
|
|
1441
|
+
let code = `
|
|
1442
|
+
import { pathToFileURL } from 'url'
|
|
1443
|
+
let { default: WOrm } = await import(pathToFileURL(process.env.SRC).href)
|
|
1444
|
+
let wo = WOrm({ url: process.env.URL, db: process.env.DB, cl: process.env.CL })
|
|
1445
|
+
let payload = JSON.parse(process.env.PAYLOAD)
|
|
1446
|
+
let tag = process.env.TAG
|
|
1447
|
+
if (process.env.MODE === 'insert') {
|
|
1448
|
+
let n = 0
|
|
1449
|
+
for (let time of payload) {
|
|
1450
|
+
let r = await wo.insert({ time, name: tag, value: 1 })
|
|
1451
|
+
n += r.nInserted
|
|
1452
|
+
}
|
|
1453
|
+
console.log(JSON.stringify({ tag, nInserted: n }))
|
|
1454
|
+
}
|
|
1455
|
+
else {
|
|
1456
|
+
let nInserted = 0
|
|
1457
|
+
let nModified = 0
|
|
1458
|
+
for (let f of payload.fields) {
|
|
1459
|
+
let r = await wo.save({ time: payload.time, [f]: tag })
|
|
1460
|
+
nInserted += r[0].nInserted
|
|
1461
|
+
nModified += r[0].nModified
|
|
1462
|
+
}
|
|
1463
|
+
console.log(JSON.stringify({ tag, nInserted, nModified }))
|
|
1464
|
+
}
|
|
1465
|
+
`
|
|
1466
|
+
let out = ''
|
|
1467
|
+
let p = spawn(process.execPath, ['--input-type=module', '-e', code], {
|
|
1468
|
+
shell: false,
|
|
1469
|
+
env: {
|
|
1470
|
+
...process.env,
|
|
1471
|
+
SRC: path.resolve('./src/WOrmPostgresql.mjs'),
|
|
1472
|
+
URL: opt.url,
|
|
1473
|
+
DB: opt.db,
|
|
1474
|
+
CL: opt.cl,
|
|
1475
|
+
MODE: mode,
|
|
1476
|
+
PAYLOAD: JSON.stringify(payload),
|
|
1477
|
+
TAG: tag,
|
|
1478
|
+
},
|
|
1479
|
+
})
|
|
1480
|
+
p.stdout.on('data', function(d) {
|
|
1481
|
+
out += d.toString()
|
|
1482
|
+
})
|
|
1483
|
+
p.stderr.on('data', function(d) {
|
|
1484
|
+
out += d.toString()
|
|
1485
|
+
})
|
|
1486
|
+
p.on('close', function() {
|
|
1487
|
+
let r = null
|
|
1488
|
+
try {
|
|
1489
|
+
r = JSON.parse(out.trim())
|
|
1490
|
+
}
|
|
1491
|
+
catch (err) {
|
|
1492
|
+
r = { tag, error: out.trim() }
|
|
1493
|
+
}
|
|
1494
|
+
resolve(r)
|
|
1495
|
+
})
|
|
1496
|
+
})
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
describe('cross-process concurrency', function() {
|
|
1501
|
+
let vans = {}
|
|
1502
|
+
let vget = {}
|
|
1503
|
+
|
|
1504
|
+
before(async function() {
|
|
1505
|
+
this.timeout(300000)
|
|
1506
|
+
|
|
1507
|
+
//跨行程insert, 兩行程各自對相同20個time插入, nInserted總和須為20且資料表僅20筆
|
|
1508
|
+
let clIns = 'xprocinsert'
|
|
1509
|
+
let optIns = genOpt(clIns)
|
|
1510
|
+
let woIns = WOrm(optIns)
|
|
1511
|
+
await woIns.createTable(clIns, 'time', {
|
|
1512
|
+
time: '2000-01-01T00:00:00Z',
|
|
1513
|
+
name: 'abc',
|
|
1514
|
+
value: 0.1,
|
|
1515
|
+
}).catch(() => {})
|
|
1516
|
+
await woIns.delAll()
|
|
1517
|
+
|
|
1518
|
+
let times = Array.from({ length: 20 }, (v, k) => {
|
|
1519
|
+
return `2025-06-01T00:${`${k}`.padStart(2, '0')}:00Z`
|
|
1520
|
+
})
|
|
1521
|
+
let rps = await Promise.all([
|
|
1522
|
+
runProc('p1', optIns, 'insert', times),
|
|
1523
|
+
runProc('p2', optIns, 'insert', times),
|
|
1524
|
+
])
|
|
1525
|
+
vget[1] = rps.filter((v) => v.error !== undefined).length
|
|
1526
|
+
vget[2] = rps.reduce((sum, v) => sum + (v.nInserted || 0), 0)
|
|
1527
|
+
vget[3] = (await woIns.select()).length
|
|
1528
|
+
|
|
1529
|
+
//跨行程save對同一time之不同欄位, 兩行程各寫5欄, 10欄須全數保留
|
|
1530
|
+
let clSav = 'xprocsave'
|
|
1531
|
+
let optSav = genOpt(clSav)
|
|
1532
|
+
let woSav = WOrm(optSav)
|
|
1533
|
+
let vSample = { time: '2000-01-01T00:00:00Z' }
|
|
1534
|
+
Array.from({ length: 10 }, (v, k) => k).forEach((k) => {
|
|
1535
|
+
vSample[`f${k}`] = 'abc'
|
|
1536
|
+
})
|
|
1537
|
+
await woSav.createTable(clSav, 'time', vSample).catch(() => {})
|
|
1538
|
+
await woSav.delAll()
|
|
1539
|
+
|
|
1540
|
+
let tSav = '2025-06-02T00:00:00Z'
|
|
1541
|
+
let rpss = await Promise.all([
|
|
1542
|
+
runProc('p1', optSav, 'save', { time: tSav, fields: ['f0', 'f1', 'f2', 'f3', 'f4'] }),
|
|
1543
|
+
runProc('p2', optSav, 'save', { time: tSav, fields: ['f5', 'f6', 'f7', 'f8', 'f9'] }),
|
|
1544
|
+
])
|
|
1545
|
+
vget[4] = rpss.filter((v) => v.error !== undefined).length
|
|
1546
|
+
|
|
1547
|
+
//兩行程對同一新time各自save, 恰有一方之nInserted為1
|
|
1548
|
+
vget[5] = rpss.reduce((sum, v) => sum + (v.nInserted || 0), 0)
|
|
1549
|
+
|
|
1550
|
+
//10欄須全數保留且值為各自行程之tag
|
|
1551
|
+
let vSav = await woSav.selectByPk(tSav)
|
|
1552
|
+
vget[6] = Array.from({ length: 10 }, (v, k) => k)
|
|
1553
|
+
.filter((k) => {
|
|
1554
|
+
let tag = k <= 4 ? 'p1' : 'p2'
|
|
1555
|
+
return vSav[`f${k}`] === tag
|
|
1556
|
+
}).length
|
|
1557
|
+
|
|
1558
|
+
})
|
|
1559
|
+
|
|
1560
|
+
vans[1] = 0
|
|
1561
|
+
it(`should get ${JSON.stringify(vans[1])} for count of errored process in cross-process insert`, async function() {
|
|
1562
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
1563
|
+
})
|
|
1564
|
+
|
|
1565
|
+
vans[2] = 20
|
|
1566
|
+
it(`should get ${JSON.stringify(vans[2])} for sum of nInserted by 2 processes inserting same 20 times`, async function() {
|
|
1567
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
1568
|
+
})
|
|
1569
|
+
|
|
1570
|
+
vans[3] = 20
|
|
1571
|
+
it(`should get ${JSON.stringify(vans[3])} for records after cross-process insert`, async function() {
|
|
1572
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
1573
|
+
})
|
|
1574
|
+
|
|
1575
|
+
vans[4] = 0
|
|
1576
|
+
it(`should get ${JSON.stringify(vans[4])} for count of errored process in cross-process save`, async function() {
|
|
1577
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
1578
|
+
})
|
|
1579
|
+
|
|
1580
|
+
vans[5] = 1
|
|
1581
|
+
it(`should get ${JSON.stringify(vans[5])} for sum of nInserted by 2 processes saving same new time`, async function() {
|
|
1582
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
1583
|
+
})
|
|
1584
|
+
|
|
1585
|
+
vans[6] = 10
|
|
1586
|
+
it(`should get ${JSON.stringify(vans[6])} for count of fields kept after cross-process save with different field`, async function() {
|
|
1587
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
1588
|
+
})
|
|
1589
|
+
|
|
1590
|
+
})
|