w-orm-postgresql 1.0.9 → 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 +8 -8
- package/dist/w-orm-postgresql.umd.js +2 -2
- package/dist/w-orm-postgresql.umd.js.map +1 -1
- package/docs/WOrmPostgresql.html +119 -27
- package/docs/WOrmPostgresql.mjs.html +93 -49
- package/docs/index.html +2 -2
- package/g-basic.mjs +8 -8
- package/package.json +1 -1
- package/src/WOrmPostgresql.mjs +91 -47
- package/test/api-postgresql.test.mjs +168 -36
package/src/WOrmPostgresql.mjs
CHANGED
|
@@ -26,11 +26,16 @@ import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
|
26
26
|
/**
|
|
27
27
|
* 操作資料庫(PostgreSQL)
|
|
28
28
|
*
|
|
29
|
+
* 本套件之主鍵欄位由opt.pk指定,預設為time,select以外之五函數皆以此欄位認定主鍵。
|
|
30
|
+
* 因主鍵得為承載業務語義之欄位(如時序資料之觀測時間),insert與save於輸入未帶有效主鍵值時不自動補值,
|
|
31
|
+
* 一律以reject回報;del亦不補值,該筆回ok:0與err。
|
|
32
|
+
*
|
|
29
33
|
* @class
|
|
30
34
|
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
31
35
|
* @param {String} [opt.url='postgresql://127.0.0.1:5432'] 輸入連接資料庫字串,預設'postgresql://127.0.0.1:5432'
|
|
32
36
|
* @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
|
|
33
37
|
* @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
|
|
38
|
+
* @param {String} [opt.pk='time'] 輸入主鍵欄位名字串,預設'time'
|
|
34
39
|
* @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
|
|
35
40
|
* @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
|
|
36
41
|
*/
|
|
@@ -63,6 +68,26 @@ function WOrmPostgresql(opt = {}) {
|
|
|
63
68
|
useCache = false
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
//pkName, 主鍵欄位名, select以外之五函數皆以此欄位認定主鍵, 預設為time
|
|
72
|
+
let pkName = get(opt, 'pk')
|
|
73
|
+
if (!isestr(pkName)) {
|
|
74
|
+
pkName = 'time'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//checkPk, 判定主鍵值是否有效
|
|
78
|
+
//因主鍵欄位得由呼叫端指定, 其型別隨欄位而異, 故此處僅要求有給值而不限定型別,
|
|
79
|
+
//型別與欄位不符者由PostgreSQL回報, 各函數再依其規格處置
|
|
80
|
+
function checkPk(pkv) {
|
|
81
|
+
return isestr(pkv) || isnum(pkv) || isbol(pkv) || isDate(pkv)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//isErrPkType, 判定錯誤是否為[主鍵值型別與欄位不符]
|
|
85
|
+
//22P02為invalid_text_representation, 22007為invalid_datetime_format
|
|
86
|
+
function isErrPkType(err) {
|
|
87
|
+
let code = get(err, 'code')
|
|
88
|
+
return code === '22P02' || code === '22007'
|
|
89
|
+
}
|
|
90
|
+
|
|
66
91
|
//getValueType
|
|
67
92
|
function getValueType(value) {
|
|
68
93
|
if (isstr(value)) {
|
|
@@ -115,7 +140,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
115
140
|
let allKeys = keys(obj)
|
|
116
141
|
|
|
117
142
|
//conflictKeys
|
|
118
|
-
let conflictKeys = [
|
|
143
|
+
let conflictKeys = [pkName]
|
|
119
144
|
|
|
120
145
|
//updateKeys
|
|
121
146
|
let updateKeys = filter(allKeys, (k) => {
|
|
@@ -190,14 +215,22 @@ function WOrmPostgresql(opt = {}) {
|
|
|
190
215
|
|
|
191
216
|
/**
|
|
192
217
|
* 創建資料表
|
|
218
|
+
* 註: pk未給時採建構時之opt.pk。因insert與save倚賴主鍵之唯一約束達成原子性,
|
|
219
|
+
* 若給予與opt.pk不同之欄位,將建出其餘函數無法正確操作之資料表,故一般應留空或給予相同值
|
|
193
220
|
*
|
|
194
221
|
* @memberOf WOrmPostgresql
|
|
195
222
|
* @param {String} cl 輸入資料表名字串
|
|
196
|
-
* @param {String} pk
|
|
223
|
+
* @param {String} [pk=opt.pk] 輸入主鍵欄位名字串,預設為建構時之opt.pk
|
|
197
224
|
* @param {Array|Object} arr 輸入數據物件陣列或數據物件
|
|
198
225
|
* @returns {Promise} 回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息
|
|
199
226
|
*/
|
|
200
227
|
async function createTable(cl, pk, arr) {
|
|
228
|
+
|
|
229
|
+
//pk, 未給時採建構時設定之主鍵欄位
|
|
230
|
+
if (!isestr(pk)) {
|
|
231
|
+
pk = pkName
|
|
232
|
+
}
|
|
233
|
+
|
|
201
234
|
let isErr = false
|
|
202
235
|
let res = null
|
|
203
236
|
|
|
@@ -368,29 +401,29 @@ function WOrmPostgresql(opt = {}) {
|
|
|
368
401
|
}
|
|
369
402
|
|
|
370
403
|
/**
|
|
371
|
-
*
|
|
404
|
+
* 由主鍵查詢單筆數據,因直接由資料表主鍵索引取值,不需如select提取數據至前端再處理,故數據量大時效能較佳
|
|
405
|
+
* 註: 本套件之主鍵欄位由建構時之opt.pk指定,預設為time
|
|
372
406
|
*
|
|
373
|
-
* 函數名依主鍵欄位命名為selectByTime而不另掛selectById別名,因本套件之主鍵為具業務語義之time,
|
|
374
|
-
* 與以無語義識別碼為主鍵者性質不同,該差異須由呼叫端知悉而非由別名掩蓋。
|
|
375
407
|
* 主鍵未命中或主鍵值無效皆回傳null而不reject,命中之判定基準與insert、save、del內對既有數據之認定一致。
|
|
408
|
+
* 主鍵值之型別與欄位不符時PostgreSQL回報22P02或22007,亦屬主鍵值無效而回傳null。
|
|
376
409
|
*
|
|
377
410
|
* @memberOf WOrmPostgresql
|
|
378
|
-
* @param {String|Date}
|
|
379
|
-
* @returns {Promise} 回傳Promise,resolve
|
|
411
|
+
* @param {String|Number|Date} pk 輸入主鍵值,即數據之主鍵欄位值
|
|
412
|
+
* @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵則回傳null,reject回傳錯誤訊息
|
|
380
413
|
*/
|
|
381
|
-
async function
|
|
414
|
+
async function selectByPk(pk) {
|
|
382
415
|
let isErr = false
|
|
383
416
|
let res = null
|
|
384
417
|
|
|
385
418
|
//check
|
|
386
|
-
if (!
|
|
387
|
-
//未給有效主鍵值視為查無數據, 判定基準與insert、save、del
|
|
419
|
+
if (!checkPk(pk)) {
|
|
420
|
+
//未給有效主鍵值視為查無數據, 判定基準與insert、save、del內對主鍵之認定一致
|
|
388
421
|
return null
|
|
389
422
|
}
|
|
390
423
|
|
|
391
|
-
//find,
|
|
424
|
+
//find, 以建構時設定之主鍵欄位查找, 與insert、save、del、genConflictSQL之認定一致
|
|
392
425
|
let find = {
|
|
393
|
-
|
|
426
|
+
[pkName]: pk,
|
|
394
427
|
}
|
|
395
428
|
|
|
396
429
|
//cache, 與select共用快取, 因快取鍵值由find與order組成, 故此處等同select(find)之結果
|
|
@@ -444,7 +477,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
444
477
|
|
|
445
478
|
//check
|
|
446
479
|
if (!isarr(rows)) {
|
|
447
|
-
throw new Error(`can not select by
|
|
480
|
+
throw new Error(`can not select by pk[${pk}]`)
|
|
448
481
|
}
|
|
449
482
|
|
|
450
483
|
//cache
|
|
@@ -460,14 +493,22 @@ function WOrmPostgresql(opt = {}) {
|
|
|
460
493
|
res = v
|
|
461
494
|
}
|
|
462
495
|
else {
|
|
463
|
-
|
|
496
|
+
//不存在主鍵, 回傳null
|
|
464
497
|
res = null
|
|
465
498
|
}
|
|
466
499
|
|
|
467
500
|
}
|
|
468
501
|
catch (err) {
|
|
469
|
-
|
|
470
|
-
|
|
502
|
+
|
|
503
|
+
//check, 主鍵值之型別與欄位不符者亦屬主鍵值無效, 依規格回傳null而不reject
|
|
504
|
+
if (isErrPkType(err)) {
|
|
505
|
+
res = null
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
isErr = true
|
|
509
|
+
res = err
|
|
510
|
+
}
|
|
511
|
+
|
|
471
512
|
}
|
|
472
513
|
finally {
|
|
473
514
|
await client.end()
|
|
@@ -484,11 +525,11 @@ function WOrmPostgresql(opt = {}) {
|
|
|
484
525
|
}
|
|
485
526
|
|
|
486
527
|
/**
|
|
487
|
-
*
|
|
528
|
+
* 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
|
|
488
529
|
*
|
|
489
530
|
* n為輸入筆數,即本次嘗試插入之基準;nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果,
|
|
490
|
-
*
|
|
491
|
-
*
|
|
531
|
+
* 不視為錯誤。同批含重複主鍵時僅首筆計入nInserted。
|
|
532
|
+
* 因主鍵得承載業務語義,未帶有效主鍵值時不自動補值,一律以reject回報。
|
|
492
533
|
*
|
|
493
534
|
* @memberOf WOrmPostgresql
|
|
494
535
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
@@ -538,10 +579,10 @@ function WOrmPostgresql(opt = {}) {
|
|
|
538
579
|
data = [data]
|
|
539
580
|
}
|
|
540
581
|
|
|
541
|
-
//check
|
|
582
|
+
//check pk, 因主鍵得承載業務語義故不自動補值, 未帶有效主鍵值者屬整批性錯誤
|
|
542
583
|
data = map(data, function(v, k) {
|
|
543
|
-
if (!
|
|
544
|
-
throw new Error(`invalid data[${k}]
|
|
584
|
+
if (!checkPk(get(v, pkName))) {
|
|
585
|
+
throw new Error(`invalid data[${k}].${pkName}[${get(v, pkName)}]`)
|
|
545
586
|
}
|
|
546
587
|
return v
|
|
547
588
|
})
|
|
@@ -556,10 +597,10 @@ function WOrmPostgresql(opt = {}) {
|
|
|
556
597
|
// console.log('mr.query', mr.query)
|
|
557
598
|
// console.log('mr.values', mr.values)
|
|
558
599
|
|
|
559
|
-
//添加conflict,
|
|
560
|
-
//由PostgreSQL於單一語句內原子完成[
|
|
561
|
-
|
|
562
|
-
let sql = `${mr.query} ON CONFLICT (
|
|
600
|
+
//添加conflict, 令已存在主鍵者跳過而不中斷整批插入, 主鍵認定與genConflictSQL一致
|
|
601
|
+
//由PostgreSQL於單一語句內原子完成[檢查主鍵未存在]與[寫入], 併發時同一主鍵僅有一次成功,
|
|
602
|
+
//同批含重複主鍵時亦僅首筆成功, 故不須逐筆插入即可取得實際插入筆數
|
|
603
|
+
let sql = `${mr.query} ON CONFLICT (${pkName}) DO NOTHING`
|
|
563
604
|
|
|
564
605
|
//nAll
|
|
565
606
|
let nAll = size(data)
|
|
@@ -623,7 +664,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
623
664
|
* [內容相同]之判定基準為待寫入物件合併進現值後與現值相同,非待寫入物件與現值全等,
|
|
624
665
|
* 故只給部份欄位且該些欄位值皆與現值相同時,合併結果等於現值,nModified為0,
|
|
625
666
|
* 令nModified忠實反映資料庫端是否真的寫入。
|
|
626
|
-
*
|
|
667
|
+
* 因主鍵得承載業務語義,未帶有效主鍵值時不自動補值,一律以reject回報。
|
|
627
668
|
*
|
|
628
669
|
* @memberOf WOrmPostgresql
|
|
629
670
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
@@ -674,10 +715,10 @@ function WOrmPostgresql(opt = {}) {
|
|
|
674
715
|
data = [data]
|
|
675
716
|
}
|
|
676
717
|
|
|
677
|
-
//check
|
|
718
|
+
//check pk, 因主鍵得承載業務語義故不自動補值, 未帶有效主鍵值者屬整批性錯誤
|
|
678
719
|
data = map(data, function(v, k) {
|
|
679
|
-
if (!
|
|
680
|
-
throw new Error(`invalid data[${k}]
|
|
720
|
+
if (!checkPk(get(v, pkName))) {
|
|
721
|
+
throw new Error(`invalid data[${k}].${pkName}[${get(v, pkName)}]`)
|
|
681
722
|
}
|
|
682
723
|
return v
|
|
683
724
|
})
|
|
@@ -688,6 +729,9 @@ function WOrmPostgresql(opt = {}) {
|
|
|
688
729
|
//rest
|
|
689
730
|
let rest = null
|
|
690
731
|
|
|
732
|
+
//pkv
|
|
733
|
+
let pkv = get(v, pkName)
|
|
734
|
+
|
|
691
735
|
//_v
|
|
692
736
|
let _v = null
|
|
693
737
|
if (true) {
|
|
@@ -697,7 +741,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
697
741
|
type: 'select',
|
|
698
742
|
table: cl,
|
|
699
743
|
where: {
|
|
700
|
-
|
|
744
|
+
[pkName]: pkv,
|
|
701
745
|
},
|
|
702
746
|
// limit: 10,
|
|
703
747
|
// order,
|
|
@@ -726,7 +770,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
726
770
|
if (iseobj(_v)) {
|
|
727
771
|
|
|
728
772
|
//_vt
|
|
729
|
-
let _vt = omit(_v,
|
|
773
|
+
let _vt = omit(_v, pkName)
|
|
730
774
|
// console.log('_vt', _vt)
|
|
731
775
|
|
|
732
776
|
//vt, [內容相同]之判定基準為[待寫入物件合併進現值後與現值相同],
|
|
@@ -734,7 +778,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
734
778
|
//合併採淺層覆蓋, 與後端以EXCLUDED整欄取代之寫入行為一致
|
|
735
779
|
let vt = {
|
|
736
780
|
..._vt,
|
|
737
|
-
...omit(v,
|
|
781
|
+
...omit(v, pkName),
|
|
738
782
|
}
|
|
739
783
|
// console.log('vt', vt)
|
|
740
784
|
|
|
@@ -789,11 +833,11 @@ function WOrmPostgresql(opt = {}) {
|
|
|
789
833
|
// console.log('mr.query', mr.query)
|
|
790
834
|
// console.log('mr.values', mr.values)
|
|
791
835
|
|
|
792
|
-
//添加conflict,
|
|
793
|
-
//此時退回DO NOTHING,
|
|
836
|
+
//添加conflict, 僅有主鍵欄位時genConflictSQL無可更新欄位而回傳空字串,
|
|
837
|
+
//此時退回DO NOTHING, 以免併發插入同一主鍵者報錯
|
|
794
838
|
let conflict = genConflictSQL(v)
|
|
795
839
|
if (!isestr(conflict)) {
|
|
796
|
-
conflict =
|
|
840
|
+
conflict = `ON CONFLICT (${pkName}) DO NOTHING`
|
|
797
841
|
}
|
|
798
842
|
|
|
799
843
|
//sql, xmax為0表該列由本語句插入, 非0表係由DO UPDATE更新而來,
|
|
@@ -864,15 +908,15 @@ function WOrmPostgresql(opt = {}) {
|
|
|
864
908
|
}
|
|
865
909
|
else {
|
|
866
910
|
//未開啟autoInsert則僅能更新既有數據, 不可用INSERT以免無中生有,
|
|
867
|
-
//由UPDATE...WHERE
|
|
911
|
+
//由UPDATE...WHERE主鍵原子完成[查找主鍵]與[更新], 併發時不會遺失更新
|
|
868
912
|
|
|
869
913
|
//mr
|
|
870
914
|
let mr = mongoSql.sql({
|
|
871
915
|
type: 'update',
|
|
872
916
|
table: cl,
|
|
873
|
-
updates: omit(v,
|
|
917
|
+
updates: omit(v, pkName),
|
|
874
918
|
where: {
|
|
875
|
-
|
|
919
|
+
[pkName]: pkv,
|
|
876
920
|
},
|
|
877
921
|
})
|
|
878
922
|
// console.log('mr', mr)
|
|
@@ -967,7 +1011,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
967
1011
|
*
|
|
968
1012
|
* 回傳陣列恆與輸入等長,各筆之n與nDeleted皆為主鍵命中筆數,值為0或1,未命中為0且屬正常結果。
|
|
969
1013
|
* 判斷某筆是否真的被刪除一律以nDeleted為準。
|
|
970
|
-
*
|
|
1014
|
+
* 未帶有效主鍵值者為該筆之輸入問題,回ok:0與err且不送查詢,不中斷其餘筆數,
|
|
971
1015
|
* 以此與[主鍵未命中]之ok:1區辨。
|
|
972
1016
|
*
|
|
973
1017
|
* @memberOf WOrmPostgresql
|
|
@@ -1024,19 +1068,19 @@ function WOrmPostgresql(opt = {}) {
|
|
|
1024
1068
|
//rest
|
|
1025
1069
|
let rest = null
|
|
1026
1070
|
|
|
1027
|
-
//
|
|
1028
|
-
let
|
|
1071
|
+
//pkv
|
|
1072
|
+
let pkv = get(v, pkName)
|
|
1029
1073
|
|
|
1030
|
-
//check
|
|
1074
|
+
//check pk, 未帶有效主鍵者不補值亦不送查詢,
|
|
1031
1075
|
//因mongo-sql會將無效值轉為null而誤中其他數據, 故直接視為該筆無法處理
|
|
1032
|
-
if (!
|
|
1076
|
+
if (!checkPk(pkv)) {
|
|
1033
1077
|
|
|
1034
1078
|
//rest
|
|
1035
1079
|
rest = {
|
|
1036
1080
|
n: 0,
|
|
1037
1081
|
nDeleted: 0,
|
|
1038
1082
|
ok: 0,
|
|
1039
|
-
err: `invalid
|
|
1083
|
+
err: `invalid ${pkName}[${pkv}]`,
|
|
1040
1084
|
}
|
|
1041
1085
|
|
|
1042
1086
|
return rest
|
|
@@ -1047,7 +1091,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
1047
1091
|
type: 'delete',
|
|
1048
1092
|
table: cl,
|
|
1049
1093
|
where: {
|
|
1050
|
-
|
|
1094
|
+
[pkName]: pkv,
|
|
1051
1095
|
},
|
|
1052
1096
|
})
|
|
1053
1097
|
// console.log('mr', mr)
|
|
@@ -1228,7 +1272,7 @@ function WOrmPostgresql(opt = {}) {
|
|
|
1228
1272
|
//save
|
|
1229
1273
|
ee.createTable = createTable
|
|
1230
1274
|
ee.select = select
|
|
1231
|
-
ee.
|
|
1275
|
+
ee.selectByPk = selectByPk
|
|
1232
1276
|
ee.insert = insert
|
|
1233
1277
|
ee.save = save
|
|
1234
1278
|
ee.del = del
|
|
@@ -373,47 +373,47 @@ describe('basic', function() {
|
|
|
373
373
|
})
|
|
374
374
|
vget[6] = rt
|
|
375
375
|
|
|
376
|
-
//
|
|
376
|
+
//selectByPk
|
|
377
377
|
rt = null
|
|
378
378
|
// vans[18] = { time: new Date('2025-01-01T00:10:00.000Z'), name: 'rosemary(modify)', value: 113.98 }
|
|
379
|
-
await wo.
|
|
379
|
+
await wo.selectByPk('2025-01-01T00:10:00Z')
|
|
380
380
|
.then(function(msg) {
|
|
381
|
-
// console.log('
|
|
382
|
-
//
|
|
381
|
+
// console.log('selectByPk then', msg)
|
|
382
|
+
// selectByPk { time: 2025-01-01T00:10:00.000Z, name: 'rosemary(modify)', value: 113.98 }
|
|
383
383
|
rt = msg
|
|
384
384
|
})
|
|
385
385
|
.catch(function(msg) {
|
|
386
|
-
// console.log('
|
|
386
|
+
// console.log('selectByPk catch', msg)
|
|
387
387
|
rt = msg.toString()
|
|
388
388
|
})
|
|
389
389
|
vget[18] = rt
|
|
390
390
|
|
|
391
|
-
//
|
|
391
|
+
//selectByPk by pk not existed
|
|
392
392
|
rt = null
|
|
393
393
|
// vans[19] = null
|
|
394
|
-
await wo.
|
|
394
|
+
await wo.selectByPk('2024-01-01T00:00:00Z')
|
|
395
395
|
.then(function(msg) {
|
|
396
|
-
// console.log('
|
|
397
|
-
//
|
|
396
|
+
// console.log('selectByPk by pk not existed then', msg)
|
|
397
|
+
// selectByPk by pk not existed null
|
|
398
398
|
rt = msg
|
|
399
399
|
})
|
|
400
400
|
.catch(function(msg) {
|
|
401
|
-
// console.log('
|
|
401
|
+
// console.log('selectByPk by pk not existed catch', msg)
|
|
402
402
|
rt = msg.toString()
|
|
403
403
|
})
|
|
404
404
|
vget[19] = rt
|
|
405
405
|
|
|
406
|
-
//
|
|
406
|
+
//selectByPk by pk invalid, 未給有效主鍵值視為查無數據
|
|
407
407
|
rt = null
|
|
408
408
|
// vans[20] = null
|
|
409
|
-
await wo.
|
|
409
|
+
await wo.selectByPk('abc')
|
|
410
410
|
.then(function(msg) {
|
|
411
|
-
// console.log('
|
|
412
|
-
//
|
|
411
|
+
// console.log('selectByPk by pk invalid then', msg)
|
|
412
|
+
// selectByPk by pk invalid null
|
|
413
413
|
rt = msg
|
|
414
414
|
})
|
|
415
415
|
.catch(function(msg) {
|
|
416
|
-
// console.log('
|
|
416
|
+
// console.log('selectByPk by pk invalid catch', msg)
|
|
417
417
|
rt = msg.toString()
|
|
418
418
|
})
|
|
419
419
|
vget[20] = rt
|
|
@@ -674,30 +674,30 @@ describe('basic', function() {
|
|
|
674
674
|
})
|
|
675
675
|
vget[17] = rt
|
|
676
676
|
|
|
677
|
-
//
|
|
677
|
+
//selectByPk (1st time, 從DB讀取並填入快取)
|
|
678
678
|
rt = null
|
|
679
679
|
// vans[21] = { time: new Date('2025-01-01T00:01:00.000Z'), name: 'rosemary(modify)', value: 654.321 }
|
|
680
|
-
await woc.
|
|
680
|
+
await woc.selectByPk('2025-01-01T00:01:00Z')
|
|
681
681
|
.then(function(msg) {
|
|
682
|
-
// console.log('
|
|
682
|
+
// console.log('selectByPk 1st then', msg)
|
|
683
683
|
rt = msg
|
|
684
684
|
})
|
|
685
685
|
.catch(function(msg) {
|
|
686
|
-
// console.log('
|
|
686
|
+
// console.log('selectByPk 1st catch', msg)
|
|
687
687
|
rt = msg.toString()
|
|
688
688
|
})
|
|
689
689
|
vget[21] = rt
|
|
690
690
|
|
|
691
|
-
//
|
|
691
|
+
//selectByPk (2nd time, 命中快取, 內容須與第一次相同)
|
|
692
692
|
rt = null
|
|
693
693
|
// vans[22] = vans[21]
|
|
694
|
-
await woc.
|
|
694
|
+
await woc.selectByPk('2025-01-01T00:01:00Z')
|
|
695
695
|
.then(function(msg) {
|
|
696
|
-
// console.log('
|
|
696
|
+
// console.log('selectByPk 2nd then', msg)
|
|
697
697
|
rt = msg
|
|
698
698
|
})
|
|
699
699
|
.catch(function(msg) {
|
|
700
|
-
// console.log('
|
|
700
|
+
// console.log('selectByPk 2nd catch', msg)
|
|
701
701
|
rt = msg.toString()
|
|
702
702
|
})
|
|
703
703
|
vget[22] = rt
|
|
@@ -758,19 +758,19 @@ describe('basic', function() {
|
|
|
758
758
|
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
759
759
|
})
|
|
760
760
|
|
|
761
|
-
//vans[18]~vans[22]為後續新增之
|
|
761
|
+
//vans[18]~vans[22]為後續新增之selectByPk, 為不更動既有編號故接續於末號之後
|
|
762
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
|
|
763
|
+
it(`should get ${JSON.stringify(vans[18])} for selectByPk`, async function() {
|
|
764
764
|
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
765
765
|
})
|
|
766
766
|
|
|
767
767
|
vans[19] = null
|
|
768
|
-
it(`should get ${JSON.stringify(vans[19])} for
|
|
768
|
+
it(`should get ${JSON.stringify(vans[19])} for selectByPk by pk not existed`, async function() {
|
|
769
769
|
assert.strict.deepStrictEqual(vget[19], vans[19])
|
|
770
770
|
})
|
|
771
771
|
|
|
772
772
|
vans[20] = null
|
|
773
|
-
it(`should get ${JSON.stringify(vans[20])} for
|
|
773
|
+
it(`should get ${JSON.stringify(vans[20])} for selectByPk by pk invalid`, async function() {
|
|
774
774
|
assert.strict.deepStrictEqual(vget[20], vans[20])
|
|
775
775
|
})
|
|
776
776
|
|
|
@@ -877,12 +877,12 @@ describe('basic', function() {
|
|
|
877
877
|
})
|
|
878
878
|
|
|
879
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
|
|
880
|
+
it(`should get ${JSON.stringify(vans[21])} for selectByPk (1st, fill cache)`, async function() {
|
|
881
881
|
assert.strict.deepStrictEqual(vget[21], vans[21])
|
|
882
882
|
})
|
|
883
883
|
|
|
884
884
|
vans[22] = vans[21]
|
|
885
|
-
it(`should get ${JSON.stringify(vans[22])} for
|
|
885
|
+
it(`should get ${JSON.stringify(vans[22])} for selectByPk (2nd, cache hit)`, async function() {
|
|
886
886
|
assert.strict.deepStrictEqual(vget[22], vans[22])
|
|
887
887
|
})
|
|
888
888
|
|
|
@@ -954,7 +954,7 @@ describe('insert nInserted', function() {
|
|
|
954
954
|
vget[7] = ss.length
|
|
955
955
|
|
|
956
956
|
//同批重複者僅首筆入庫
|
|
957
|
-
let vdup = await wo.
|
|
957
|
+
let vdup = await wo.selectByPk('2025-02-01T00:05:00Z')
|
|
958
958
|
vget[8] = vdup.name
|
|
959
959
|
|
|
960
960
|
})
|
|
@@ -1028,7 +1028,7 @@ describe('save concurrency', function() {
|
|
|
1028
1028
|
wo.save({ time: tMerge, name: 'merge-name' }),
|
|
1029
1029
|
wo.save({ time: tMerge, value: 99 }),
|
|
1030
1030
|
])
|
|
1031
|
-
vget[1] = await wo.
|
|
1031
|
+
vget[1] = await wo.selectByPk(tMerge)
|
|
1032
1032
|
|
|
1033
1033
|
//併發save對全新time, autoInsert僅一次且不得報錯
|
|
1034
1034
|
let tNew = '2025-03-01T00:01:00Z'
|
|
@@ -1048,12 +1048,12 @@ describe('save concurrency', function() {
|
|
|
1048
1048
|
let tDiff = '2025-03-01T00:03:00Z'
|
|
1049
1049
|
await wo.insert({ time: tDiff, name: 'diff', value: 5 })
|
|
1050
1050
|
vget[6] = await wo.save({ time: tDiff, value: 6 })
|
|
1051
|
-
vget[7] = await wo.
|
|
1051
|
+
vget[7] = await wo.selectByPk(tDiff)
|
|
1052
1052
|
|
|
1053
1053
|
//save(autoInsert=false)對不存在之time不得插入
|
|
1054
1054
|
let tNone = '2025-03-01T00:04:00Z'
|
|
1055
1055
|
vget[8] = await wo.save({ time: tNone, name: 'none' }, { autoInsert: false })
|
|
1056
|
-
vget[9] = await wo.
|
|
1056
|
+
vget[9] = await wo.selectByPk(tNone)
|
|
1057
1057
|
|
|
1058
1058
|
//併發save(autoInsert=false)對既有time之不同欄位, 各欄位皆須保留且不得報錯
|
|
1059
1059
|
let tUpd = '2025-03-01T00:05:00Z'
|
|
@@ -1063,7 +1063,7 @@ describe('save concurrency', function() {
|
|
|
1063
1063
|
wo.save({ time: tUpd, value: 88 }, { autoInsert: false }),
|
|
1064
1064
|
])
|
|
1065
1065
|
vget[10] = rsu.filter((v) => v[0].ok !== 1).length
|
|
1066
|
-
vget[11] = await wo.
|
|
1066
|
+
vget[11] = await wo.selectByPk(tUpd)
|
|
1067
1067
|
|
|
1068
1068
|
})
|
|
1069
1069
|
|
|
@@ -1148,7 +1148,7 @@ describe('spec compliance', function() {
|
|
|
1148
1148
|
let tPart = '2025-04-01T00:00:00Z'
|
|
1149
1149
|
await wo.insert({ time: tPart, name: 'peter', value: 5 })
|
|
1150
1150
|
vget[1] = await wo.save({ time: tPart, value: 5 })
|
|
1151
|
-
vget[2] = await wo.
|
|
1151
|
+
vget[2] = await wo.selectByPk(tPart)
|
|
1152
1152
|
|
|
1153
1153
|
//save單筆失敗不中斷整批, 失敗筆須帶err
|
|
1154
1154
|
let tOk = '2025-04-01T00:01:00Z'
|
|
@@ -1302,6 +1302,138 @@ describe('spec compliance', function() {
|
|
|
1302
1302
|
})
|
|
1303
1303
|
|
|
1304
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
|
+
|
|
1305
1437
|
//runProc, 另起行程對同一資料表操作, 用於驗證跨行程之原子性
|
|
1306
1438
|
//子行程以--input-type=module執行, 並以pathToFileURL轉換src路徑, 避免專案路徑含非ASCII字元而無法import
|
|
1307
1439
|
function runProc(tag, opt, mode, payload) {
|
|
@@ -1416,7 +1548,7 @@ describe('cross-process concurrency', function() {
|
|
|
1416
1548
|
vget[5] = rpss.reduce((sum, v) => sum + (v.nInserted || 0), 0)
|
|
1417
1549
|
|
|
1418
1550
|
//10欄須全數保留且值為各自行程之tag
|
|
1419
|
-
let vSav = await woSav.
|
|
1551
|
+
let vSav = await woSav.selectByPk(tSav)
|
|
1420
1552
|
vget[6] = Array.from({ length: 10 }, (v, k) => k)
|
|
1421
1553
|
.filter((k) => {
|
|
1422
1554
|
let tag = k <= 4 ? 'p1' : 'p2'
|