w-orm-reladb 1.1.12 → 1.1.13
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 +61 -1
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +35 -19
- package/docs/WOrmReladb.mjs.html +177 -58
- package/docs/cvFind.mjs.html +76 -3
- package/docs/global.html +386 -10
- package/docs/importModels.mjs.html +2 -2
- package/docs/index.html +2 -2
- package/models/twf.js +20 -0
- package/models/twfk.js +20 -0
- package/package.json +2 -2
- package/src/WOrmReladb.mjs +175 -56
- package/src/cvFind.mjs +74 -1
- package/test/api-dist.test.mjs +28 -0
- package/test/api-mssql.test.mjs +41 -7
- package/test/api-sqlite.test.mjs +2 -0
- package/test/api-wellformed.mjs +645 -0
- package/test/unit-cvfind.test.mjs +12 -0
- package/test/unit-wellformed.test.mjs +322 -0
package/models/twf.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* jshint indent: 1 */
|
|
2
|
+
module.exports = function(sequelize, DataTypes) {
|
|
3
|
+
return sequelize.define('twf', {
|
|
4
|
+
'id': {
|
|
5
|
+
type: DataTypes.STRING,
|
|
6
|
+
allowNull: false,
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
autoIncrement: false,
|
|
9
|
+
comment: null
|
|
10
|
+
},
|
|
11
|
+
'name': {
|
|
12
|
+
type: DataTypes.TEXT,
|
|
13
|
+
allowNull: true,
|
|
14
|
+
autoIncrement: false,
|
|
15
|
+
comment: null
|
|
16
|
+
}
|
|
17
|
+
}, {
|
|
18
|
+
tableName: 'twf'
|
|
19
|
+
});
|
|
20
|
+
};
|
package/models/twfk.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* jshint indent: 1 */
|
|
2
|
+
module.exports = function(sequelize, DataTypes) {
|
|
3
|
+
return sequelize.define('twfk', {
|
|
4
|
+
'key': {
|
|
5
|
+
type: DataTypes.STRING,
|
|
6
|
+
allowNull: false,
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
autoIncrement: false,
|
|
9
|
+
comment: null
|
|
10
|
+
},
|
|
11
|
+
'name': {
|
|
12
|
+
type: DataTypes.TEXT,
|
|
13
|
+
allowNull: true,
|
|
14
|
+
autoIncrement: false,
|
|
15
|
+
comment: null
|
|
16
|
+
}
|
|
17
|
+
}, {
|
|
18
|
+
tableName: 'twfk'
|
|
19
|
+
});
|
|
20
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-orm-reladb",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.13",
|
|
4
4
|
"main": "dist/w-orm-reladb.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"json5": "^2.2.3",
|
|
7
7
|
"sqlite3": "^6.0.1",
|
|
8
8
|
"tedious": "^19.2.2",
|
|
9
9
|
"w-auto-sequelize": "^1.0.49",
|
|
10
|
-
"wsemi": "^1.9.
|
|
10
|
+
"wsemi": "^1.9.24"
|
|
11
11
|
},
|
|
12
12
|
"dependenciesTemp": {
|
|
13
13
|
"@journeyapps/sqlcipher": "^5.3.1",
|
package/src/WOrmReladb.mjs
CHANGED
|
@@ -22,10 +22,11 @@ import arrHas from 'wsemi/src/arrHas.mjs'
|
|
|
22
22
|
import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
23
23
|
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
|
|
24
24
|
import pmQueue from 'wsemi/src/pmQueue.mjs'
|
|
25
|
+
import chkOrmStr from 'wsemi/src/chkOrmStr.mjs'
|
|
25
26
|
import WAutoSequelize from 'w-auto-sequelize/src/WAutoSequelize.mjs'
|
|
26
27
|
import importModels from './importModels.mjs'
|
|
27
28
|
import _genModelsByTabs from './genModelsByTabs.mjs'
|
|
28
|
-
import cvFind, { getColKind, getColScale, normPk, chkFindShape, genDateChecker } from './cvFind.mjs'
|
|
29
|
+
import cvFind, { getColKind, getColScale, normPk, chkWf, chkFindShape, genDateChecker } from './cvFind.mjs'
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
//放置於全域宣告, 若使用useStable=true才可於當創建多實例時仍能全域控管
|
|
@@ -37,6 +38,14 @@ let pmq = pmQueue(1)
|
|
|
37
38
|
*
|
|
38
39
|
* 注意: 各model內id欄位不是主鍵(primary key)時需要強制更改成為主鍵,否則sequelize無法匯入
|
|
39
40
|
*
|
|
41
|
+
* 注意: opt.url、opt.db、opt.cl、opt.storage(儲存位址名稱與路徑)含孤立代理碼元(UTF-16代理對被拆開後只剩一半,String.prototype.isWellFormed()為false)者,
|
|
42
|
+
* 建構時即拋出invalid opt.<名>: string contains lone surrogate(不回顯值):UTF-8轉換會把孤立代理碼元改寫為U+FFFD,不同名稱將指向同一儲存
|
|
43
|
+
*
|
|
44
|
+
* 注意: 寫入與主鍵查詢之字串須良構(第66項):主鍵不得含孤立代理碼元或NUL,insert、insertBulk、save之數據字串值與鍵名(含巢狀、陣列元素)不得含孤立代理碼元(值之NUL不拒);
|
|
45
|
+
* 不合格者insert與insertBulk整批reject、save與del該筆ok為0附err、selectByPk回傳null,詳見各函數說明與README。
|
|
46
|
+
* 註: mssql預設排序規則(如SQL_Latin1_General_CP1_CI_AS)下字串相等由排序規則決定,良構字串亦可能相等('a'、'a'+emoji、'a'+U+FFFD),屬資料庫語意不在檢核範圍,
|
|
47
|
+
* 需精確比對時請以二進位排序規則(如Latin1_General_100_BIN2)建表
|
|
48
|
+
*
|
|
40
49
|
* @class
|
|
41
50
|
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
42
51
|
* @param {String} [opt.url='mssql://username:password@localhost:1433'] 輸入連接資料庫字串,資料庫可選'mssql'、'sqlite'、'mysql'、'mariadb'、'postgres',預設'mssql://username:password@localhost:1433'
|
|
@@ -87,6 +96,18 @@ function WOrmReladb(opt = {}) {
|
|
|
87
96
|
if (!isestr(opt.storage)) {
|
|
88
97
|
opt.storage = './worm.db' //storage, only use for sqlite
|
|
89
98
|
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
//儲存位址名稱(opt.db、opt.cl、opt.url、opt.storage)含孤立代理碼元者建構時即拋出(第66項, R13):
|
|
102
|
+
//路徑與名稱經UTF-8轉換後孤立代理碼元被改寫為U+FFFD, 不同名稱會指向同一儲存(如兩個不同之storage路徑共用同一個sqlite檔);
|
|
103
|
+
//須先於opt.url之解析(其錯誤訊息會回顯opt.url); 訊息為固定詞彙且不回顯值, 此處不檢NUL(不屬本規則)
|
|
104
|
+
for (let k of ['db', 'cl', 'url', 'storage']) {
|
|
105
|
+
let why = chkOrmStr(opt[k], { nul: false })
|
|
106
|
+
if (why !== null) {
|
|
107
|
+
throw new Error(`invalid opt.${k}: ${why}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
90
111
|
opt.storage = path.resolve(opt.storage)
|
|
91
112
|
if (!isbol(opt.useEncryption)) {
|
|
92
113
|
opt.useEncryption = false
|
|
@@ -222,6 +243,8 @@ function WOrmReladb(opt = {}) {
|
|
|
222
243
|
* 判定主鍵值是否有效(與主鍵欄型別無關之判定)
|
|
223
244
|
* 註: select以外之五函數對主鍵之認定一律以本函數與normPkOf為準,令selectByPk之[命中]與insert、save、del內對既有數據之認定一致
|
|
224
245
|
* 註: 有效即非空字串或有限數值(含0), new String與new Number取原始值; NaN、±Infinity、布林、物件等為無效(見cvFind.mjs之normPk)
|
|
246
|
+
* 註: 含孤立代理碼元或NUL之字串亦為無效(第66項, R13),故selectByPk回null;寫入側(insert、insertBulk、save、del)於procPk之前即由chkWf逐筆檢出,
|
|
247
|
+
* 令autoGenPk為true時呼叫端所給之不合格字串不被當成[未給主鍵]而以自動產生之主鍵取代
|
|
225
248
|
*
|
|
226
249
|
* @ignore
|
|
227
250
|
* @param {*} v 輸入主鍵值
|
|
@@ -365,17 +388,22 @@ function WOrmReladb(opt = {}) {
|
|
|
365
388
|
* 正規化各筆數據之主鍵值並就地寫回, 型別不符者整批拋出
|
|
366
389
|
* 註: 須於procPk之後、任何寫入之前一次完成; 已給主鍵而型別不符屬呼叫端未履行契約, 不以自動產生之主鍵取代
|
|
367
390
|
* 註: 正規化值寫回數據, 令寫入值與其後各函數之比對值一致(如字串類主鍵欄之123存為'123'), 事件之數據亦為實際寫入者
|
|
391
|
+
* 註: 訊息只含筆數索引與主鍵欄名, 不回顯主鍵值(第66項, R13)
|
|
368
392
|
*
|
|
369
393
|
* @ignore
|
|
370
394
|
* @param {Object} md 輸入資料表model物件
|
|
371
395
|
* @param {Array} data 輸入已補齊主鍵之數據陣列
|
|
396
|
+
* @param {Array} [bads=[]] 輸入chkWf之逐筆訊息陣列, 非null者(save之不合格筆, 已另行處置為逐筆失敗)略過
|
|
372
397
|
* @returns {undefined} 無回傳值
|
|
373
398
|
*/
|
|
374
|
-
function normPks(md, data) {
|
|
399
|
+
function normPks(md, data, bads = []) {
|
|
375
400
|
data.forEach((v, k) => {
|
|
401
|
+
if (get(bads, k, null) !== null) {
|
|
402
|
+
return
|
|
403
|
+
}
|
|
376
404
|
let r = normPkOf(md, get(v, opt.pk))
|
|
377
405
|
if (r.kind !== 'ok') {
|
|
378
|
-
throw new Error(`invalid data[${k}].${opt.pk}
|
|
406
|
+
throw new Error(`invalid data[${k}].${opt.pk}, type does not match pk column [${opt.pk}]`)
|
|
379
407
|
}
|
|
380
408
|
v[opt.pk] = r.v
|
|
381
409
|
})
|
|
@@ -393,11 +421,15 @@ function WOrmReladb(opt = {}) {
|
|
|
393
421
|
* @ignore
|
|
394
422
|
* @param {Object} md 輸入資料表model物件
|
|
395
423
|
* @param {Array} data 輸入已補齊並正規化主鍵之數據陣列
|
|
424
|
+
* @param {Array} [bads=[]] 輸入chkWf之逐筆訊息陣列, 非null者(save之不合格筆, 已另行處置為逐筆失敗)略過
|
|
396
425
|
* @returns {undefined} 無回傳值
|
|
397
426
|
*/
|
|
398
|
-
function chkDates(md, data) {
|
|
427
|
+
function chkDates(md, data, bads = []) {
|
|
399
428
|
let chk = genDateChecker(get(md, 'rawAttributes', {}))
|
|
400
429
|
data.forEach((v, k) => {
|
|
430
|
+
if (get(bads, k, null) !== null) {
|
|
431
|
+
return
|
|
432
|
+
}
|
|
401
433
|
let col = chk(v)
|
|
402
434
|
if (col !== null) {
|
|
403
435
|
throw new Error(`invalid data: date is not a valid time in years 1-9999 at [data[${k}].${col}]`)
|
|
@@ -695,6 +727,7 @@ function WOrmReladb(opt = {}) {
|
|
|
695
727
|
*
|
|
696
728
|
* 註: 主鍵欄位取自opt.pk,預設為id,支援由呼叫端指定
|
|
697
729
|
* 註: 主鍵未命中或主鍵值無效皆回傳null而不reject,[命中]之判定基準與insert、save、del內對既有數據之認定一致
|
|
730
|
+
* 註: 含孤立代理碼元或NUL之字串主鍵(含new String包裝者)為無效而回傳null,不連線、不送查詢(第66項, R13): UTF-8存放之資料庫會把孤立代理碼元改寫為U+FFFD,若照送會命中他筆
|
|
698
731
|
* 註: 主鍵值依主鍵欄型別正規化後查詢: 字串類主鍵欄之有限數字以String(v)處置(selectByPk(123)即查'123'), 整數類主鍵欄之標準整數寫法字串轉為數值;
|
|
699
732
|
* 型別不符(整數類主鍵欄給'012'、12.4、'abc'等)亦屬主鍵值無效而回傳null
|
|
700
733
|
* 註: 本函數不得有副作用,故不創建資料表
|
|
@@ -723,10 +756,13 @@ function WOrmReladb(opt = {}) {
|
|
|
723
756
|
return Promise.reject(err)
|
|
724
757
|
}
|
|
725
758
|
|
|
726
|
-
//check, 未給有效主鍵值視為查無數據, 不送查詢以免無效值被轉為null
|
|
727
|
-
|
|
759
|
+
//check, 未給有效主鍵值視為查無數據, 不送查詢以免無效值被轉為null而誤中其他數據;
|
|
760
|
+
//含孤立代理碼元或NUL之字串亦為無效(R13); 包裝物件(new String、new Number)只求值一次, 其後以原始值查詢(檢查之值即使用之值)
|
|
761
|
+
let r0 = normPk(pk, 'any')
|
|
762
|
+
if (r0.kind !== 'ok') {
|
|
728
763
|
return null
|
|
729
764
|
}
|
|
765
|
+
pk = r0.v
|
|
730
766
|
|
|
731
767
|
//check
|
|
732
768
|
if (opt.useEncryption && dialect === 'sqlite') {
|
|
@@ -827,6 +863,10 @@ function WOrmReladb(opt = {}) {
|
|
|
827
863
|
* 錯誤訊息為invalid data: date is not a valid time in years 1-9999 at [data[索引].欄名],不回顯值;sqlite以文字存日期,範圍外年份之文字與時間序不一致,會使以日期為條件之delAll刪錯列,
|
|
828
864
|
* 故兩方言一律於送出前拒收;無效時間(Invalid Date、NaN、無法解析之字串、空字串等,sqlite會存為字典序大於任何年份之文字Invalid date,mssql則由資料庫報錯)亦一併reject;
|
|
829
865
|
* 庫內既有之範圍外日期不遷移,須自行清理或改寫(見README)
|
|
866
|
+
* 註: 主鍵與數據之字串須良構(第66項, R13): 主鍵不得含孤立代理碼元(UTF-16代理對被拆開後只剩一半, UTF-8存放時被改寫為U+FFFD而使不同主鍵寫成同一鍵)或NUL,
|
|
867
|
+
* 數據之字串值與鍵名(含巢狀物件、陣列元素、Map/Set等,不論欄位是否在model內)不得含孤立代理碼元(值之NUL不拒);U+FFFD本身與成對代理(emoji等)為良構,照常接受。
|
|
868
|
+
* 任一筆不合格即reject且同批皆不寫入,訊息為invalid id: 原因 at [data[索引].主鍵欄]或invalid data: 原因 at [data[索引].路徑],不回顯值;
|
|
869
|
+
* 不隨opt.autoGenPk改變(autoGenPk為true時亦不以自動產生之主鍵取代呼叫端所給之不合格主鍵),檢核先於連線與model載入,作用於複本
|
|
830
870
|
* 註: option.returnList開啟時改回與輸入等長且保序之逐筆結果陣列[{n,nInserted,ok}],
|
|
831
871
|
* 聚合計數只答得出[有幾筆是新的]而答不出[是哪幾筆],而後者正是去重之產出物;
|
|
832
872
|
* 逐筆元素之n與ok恆為1,資訊由nInserted承載,不變式為
|
|
@@ -870,9 +910,6 @@ function WOrmReladb(opt = {}) {
|
|
|
870
910
|
}
|
|
871
911
|
}
|
|
872
912
|
|
|
873
|
-
//cloneDeep
|
|
874
|
-
data = cloneDeep(data)
|
|
875
|
-
|
|
876
913
|
//isErr, res
|
|
877
914
|
let isErr = false
|
|
878
915
|
let res = null
|
|
@@ -880,13 +917,25 @@ function WOrmReladb(opt = {}) {
|
|
|
880
917
|
//si
|
|
881
918
|
let si = instance
|
|
882
919
|
|
|
920
|
+
//inited, 本次呼叫是否已進入initSequelize(僅此時finally才關閉sequelize; 預檢失敗於其前者不得關閉呼叫端先前持有之連線)
|
|
921
|
+
let inited = false
|
|
922
|
+
|
|
883
923
|
try {
|
|
884
924
|
|
|
885
925
|
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
886
926
|
procClosed(instance)
|
|
887
927
|
|
|
928
|
+
//chkWf, 主鍵與資料之字串良構(孤立代理碼元、主鍵之NUL)逐筆檢核(R13): 先於連線、model載入與任何寫入, 檢查之物件即cloneDeep後之複本且其後只使用該複本;
|
|
929
|
+
//任一筆不合格即整批拋出(以索引最小者為準)而不寫入任何一筆, 不隨autoGenPk改變, 不以自動產生之主鍵取代不合格之主鍵
|
|
930
|
+
let bads = chkWf(isarr(data) ? data : [data], opt.pk, true)
|
|
931
|
+
let iBad = bads.findIndex((v) => v !== null)
|
|
932
|
+
if (iBad >= 0) {
|
|
933
|
+
throw new Error(bads[iBad])
|
|
934
|
+
}
|
|
935
|
+
|
|
888
936
|
//initSequelize, 內部自動初始化須在try內: 其例外(如驅動缺失)與si.err同屬整批性錯誤, 方能於reject前發出error事件, 已建構之sequelize亦由finally關閉
|
|
889
937
|
if (instance === null) {
|
|
938
|
+
inited = true
|
|
890
939
|
si = await initSequelize()
|
|
891
940
|
}
|
|
892
941
|
|
|
@@ -976,7 +1025,7 @@ function WOrmReladb(opt = {}) {
|
|
|
976
1025
|
finally {
|
|
977
1026
|
|
|
978
1027
|
//closeSequelize
|
|
979
|
-
if (instance === null) { //內部自動初始化得close
|
|
1028
|
+
if (instance === null && inited) { //內部自動初始化得close
|
|
980
1029
|
await closeSequelize('insert')
|
|
981
1030
|
}
|
|
982
1031
|
|
|
@@ -1009,6 +1058,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1009
1058
|
* 註: 主鍵值依主鍵欄型別正規化後寫入(字串類主鍵欄之123存為'123');型別不符(整數類主鍵欄給'012'等)即reject且同批皆不寫入
|
|
1010
1059
|
* 註: DATE、DATEONLY欄之值換算後年份須在1–9999,範圍外或無效者(如new Date(8.64e15)、new Date(NaN))即reject且同批皆不寫入,訊息為invalid data: date is not a valid time in years 1-9999 at [data[索引].欄名],
|
|
1011
1060
|
* 不回顯值,兩方言一致於送出前拒收,規則同insert
|
|
1061
|
+
* 註: 主鍵與數據之字串須良構(孤立代理碼元、主鍵之NUL),不合格者整批reject且同批皆不寫入,規則同insert(第66項, R13)
|
|
1012
1062
|
* 註: [全有全無]為獨立於T7之額外要求——T7僅保證每筆之[檢查主鍵不存在]與[寫入]為原子,不保證整批。
|
|
1013
1063
|
* mssql因綁定參數數量上限會由驅動層自動拆為多語句送出,實測1000筆之批次於末筆撞主鍵時已有946筆落盤,
|
|
1014
1064
|
* 故一律以交易包覆並於失敗時回滾;呼叫端已給transaction時改以巢狀交易(SAVEPOINT)包覆,
|
|
@@ -1045,9 +1095,6 @@ function WOrmReladb(opt = {}) {
|
|
|
1045
1095
|
}
|
|
1046
1096
|
}
|
|
1047
1097
|
|
|
1048
|
-
//cloneDeep
|
|
1049
|
-
data = cloneDeep(data)
|
|
1050
|
-
|
|
1051
1098
|
//isErr, res
|
|
1052
1099
|
let isErr = false
|
|
1053
1100
|
let res = null
|
|
@@ -1055,13 +1102,25 @@ function WOrmReladb(opt = {}) {
|
|
|
1055
1102
|
//si
|
|
1056
1103
|
let si = instance
|
|
1057
1104
|
|
|
1105
|
+
//inited, 本次呼叫是否已進入initSequelize(僅此時finally才關閉sequelize; 預檢失敗於其前者不得關閉呼叫端先前持有之連線)
|
|
1106
|
+
let inited = false
|
|
1107
|
+
|
|
1058
1108
|
try {
|
|
1059
1109
|
|
|
1060
1110
|
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1061
1111
|
procClosed(instance)
|
|
1062
1112
|
|
|
1113
|
+
//chkWf, 主鍵與資料之字串良構(孤立代理碼元、主鍵之NUL)逐筆檢核(R13): 先於連線、model載入與任何寫入, 檢查之物件即cloneDeep後之複本且其後只使用該複本;
|
|
1114
|
+
//任一筆不合格即整批拋出(以索引最小者為準)而不寫入任何一筆, 不隨autoGenPk改變, 不以自動產生之主鍵取代不合格之主鍵
|
|
1115
|
+
let bads = chkWf(isarr(data) ? data : [data], opt.pk, true)
|
|
1116
|
+
let iBad = bads.findIndex((v) => v !== null)
|
|
1117
|
+
if (iBad >= 0) {
|
|
1118
|
+
throw new Error(bads[iBad])
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1063
1121
|
//initSequelize, 內部自動初始化須在try內: 其例外(如驅動缺失)與si.err同屬整批性錯誤, 方能於reject前發出error事件, 已建構之sequelize亦由finally關閉
|
|
1064
1122
|
if (instance === null) {
|
|
1123
|
+
inited = true
|
|
1065
1124
|
si = await initSequelize()
|
|
1066
1125
|
}
|
|
1067
1126
|
|
|
@@ -1144,7 +1203,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1144
1203
|
finally {
|
|
1145
1204
|
|
|
1146
1205
|
//closeSequelize
|
|
1147
|
-
if (instance === null) { //內部自動初始化得close
|
|
1206
|
+
if (instance === null && inited) { //內部自動初始化得close
|
|
1148
1207
|
await closeSequelize('insertBulk')
|
|
1149
1208
|
}
|
|
1150
1209
|
|
|
@@ -1181,9 +1240,10 @@ function WOrmReladb(opt = {}) {
|
|
|
1181
1240
|
* @param {Boolean} autoInsert 輸入是否於查無數據時自動改以插入處理布林值
|
|
1182
1241
|
* @param {Object|null} transaction 輸入交易(transaction)物件
|
|
1183
1242
|
* @param {Array} ks 輸入model之欄位名陣列
|
|
1243
|
+
* @param {Number} k 輸入數據於陣列內之索引,供訊息使用(訊息不回顯主鍵值)
|
|
1184
1244
|
* @returns {Promise} 回傳Promise,resolve回傳本筆儲存結果,本筆失敗時亦resolve並以ok為0回報
|
|
1185
1245
|
*/
|
|
1186
|
-
async function saveOne(md, v, autoInsert, transaction, ks) {
|
|
1246
|
+
async function saveOne(md, v, autoInsert, transaction, ks, k) {
|
|
1187
1247
|
|
|
1188
1248
|
//nTry, 預測錯誤時重試, 給予有限次數令其收斂
|
|
1189
1249
|
let nTry = 3
|
|
@@ -1322,7 +1382,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1322
1382
|
nInserted: 0,
|
|
1323
1383
|
nModified: 0,
|
|
1324
1384
|
ok: 0,
|
|
1325
|
-
err: `can not save data
|
|
1385
|
+
err: `can not save data[${k}].${opt.pk} in ${nTry} tries`,
|
|
1326
1386
|
}
|
|
1327
1387
|
}
|
|
1328
1388
|
|
|
@@ -1350,6 +1410,8 @@ function WOrmReladb(opt = {}) {
|
|
|
1350
1410
|
* 註: 主鍵值依主鍵欄型別正規化(字串類主鍵欄之123即'123');型別不符(整數類主鍵欄給'012'等)屬呼叫端未履行契約,整批reject且同批皆不寫入
|
|
1351
1411
|
* 註: DATE、DATEONLY欄之值換算後年份須在1–9999,範圍外或無效者(如new Date(8.64e15)、new Date(NaN))屬呼叫端未履行契約,整批reject且同批皆不寫入(含autoInsert為false而不會寫入者;不降為該筆ok為0),
|
|
1352
1412
|
* 訊息為invalid data: date is not a valid time in years 1-9999 at [data[索引].欄名],不回顯值,兩方言一致於送出前拒收,規則同insert
|
|
1413
|
+
* 註: 主鍵與數據之字串須良構(孤立代理碼元、主鍵之NUL;規則同insert, 第66項, R13),但不合格者為該筆失敗而非整批reject:該筆ok為0並附err(invalid id: 原因 at [data[索引].主鍵欄]或invalid data: 原因 at [data[索引].路徑],不回顯值),
|
|
1414
|
+
* 不讀取資料庫、不寫入(含autoInsert為false),同批其他筆照常處理;不隨opt.autoGenPk改變。全部筆數皆不合格時不連線
|
|
1353
1415
|
*
|
|
1354
1416
|
* @memberOf WOrmReladb
|
|
1355
1417
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
@@ -1366,9 +1428,6 @@ function WOrmReladb(opt = {}) {
|
|
|
1366
1428
|
return []
|
|
1367
1429
|
}
|
|
1368
1430
|
|
|
1369
|
-
//cloneDeep
|
|
1370
|
-
data = cloneDeep(data)
|
|
1371
|
-
|
|
1372
1431
|
//instance
|
|
1373
1432
|
let instance = get(option, 'instance', null)
|
|
1374
1433
|
|
|
@@ -1392,23 +1451,32 @@ function WOrmReladb(opt = {}) {
|
|
|
1392
1451
|
//si
|
|
1393
1452
|
let si = instance
|
|
1394
1453
|
|
|
1454
|
+
//inited, 本次呼叫是否已進入initSequelize(僅此時finally才關閉sequelize; 預檢失敗於其前者不得關閉呼叫端先前持有之連線)
|
|
1455
|
+
let inited = false
|
|
1456
|
+
|
|
1395
1457
|
try {
|
|
1396
1458
|
|
|
1397
1459
|
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1398
1460
|
procClosed(instance)
|
|
1399
1461
|
|
|
1400
|
-
//
|
|
1401
|
-
|
|
1462
|
+
//chkWf, 主鍵與資料之字串良構(孤立代理碼元、主鍵之NUL)逐筆檢核(R13): 先於連線、model載入與任何讀取、寫入、刪除, 檢查之物件即cloneDeep後之複本且其後只使用該複本;
|
|
1463
|
+
//不合格者為該筆失敗(ok為0附err, 同批其他筆照常), bads為逐筆訊息(合格者為null); 不隨autoGenPk改變
|
|
1464
|
+
let bads = chkWf(isarr(data) ? data : [data], opt.pk, true)
|
|
1465
|
+
let nOk = bads.filter((v) => v === null).length
|
|
1466
|
+
|
|
1467
|
+
//initSequelize, 內部自動初始化須在try內: 其例外(如驅動缺失)與si.err同屬整批性錯誤, 方能於reject前發出error事件, 已建構之sequelize亦由finally關閉; 全部不合格者無須連線
|
|
1468
|
+
if (instance === null && nOk > 0) {
|
|
1469
|
+
inited = true
|
|
1402
1470
|
si = await initSequelize()
|
|
1403
1471
|
}
|
|
1404
1472
|
|
|
1405
1473
|
//check
|
|
1406
|
-
if (si.err) {
|
|
1474
|
+
if (si !== null && si.err) {
|
|
1407
1475
|
throw si.err
|
|
1408
1476
|
}
|
|
1409
1477
|
|
|
1410
1478
|
//md
|
|
1411
|
-
let md = si.mds[opt.cl]
|
|
1479
|
+
let md = si === null ? null : si.mds[opt.cl]
|
|
1412
1480
|
|
|
1413
1481
|
//check
|
|
1414
1482
|
if (!isarr(data)) {
|
|
@@ -1417,21 +1485,34 @@ function WOrmReladb(opt = {}) {
|
|
|
1417
1485
|
|
|
1418
1486
|
//procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
|
|
1419
1487
|
data = map(data, (v, k) => {
|
|
1420
|
-
return procPk(md, v, k)
|
|
1488
|
+
return bads[k] !== null ? v : procPk(md, v, k)
|
|
1421
1489
|
})
|
|
1422
1490
|
|
|
1423
1491
|
//normPks, 主鍵值依主鍵欄型別正規化後寫回, 型別不符者整批reject且不寫入任何一筆
|
|
1424
|
-
normPks(md, data)
|
|
1492
|
+
normPks(md, data, bads)
|
|
1425
1493
|
|
|
1426
1494
|
//chkDates, DATE、DATEONLY欄之值須為年份在1–9999之有效時間, 範圍外或無效者整批reject且不寫入任何一筆(含autoInsert為false而不會寫入者; 與條件側共用範圍判定, 見R12)
|
|
1427
|
-
chkDates(md, data)
|
|
1495
|
+
chkDates(md, data, bads)
|
|
1428
1496
|
|
|
1429
1497
|
//ks, model之欄位名, 供合併後比對時濾除非欄位之鍵
|
|
1430
1498
|
let ks = keys(get(md, 'rawAttributes', {}))
|
|
1431
1499
|
|
|
1432
1500
|
//pmSeries
|
|
1433
|
-
res = await pmSeries(data, async(v) => {
|
|
1434
|
-
|
|
1501
|
+
res = await pmSeries(data, async(v, k) => {
|
|
1502
|
+
|
|
1503
|
+
//check, 字串不合格之筆為該筆失敗: 不讀取資料庫、不寫入, 同批其他筆照常
|
|
1504
|
+
if (bads[k] !== null) {
|
|
1505
|
+
emitError('save', [v], bads[k])
|
|
1506
|
+
return {
|
|
1507
|
+
n: 1,
|
|
1508
|
+
nInserted: 0,
|
|
1509
|
+
nModified: 0,
|
|
1510
|
+
ok: 0,
|
|
1511
|
+
err: bads[k],
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
return saveOne(md, v, autoInsert, transaction, ks, k)
|
|
1435
1516
|
})
|
|
1436
1517
|
|
|
1437
1518
|
}
|
|
@@ -1442,7 +1523,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1442
1523
|
finally {
|
|
1443
1524
|
|
|
1444
1525
|
//closeSequelize
|
|
1445
|
-
if (instance === null) { //內部自動初始化得close
|
|
1526
|
+
if (instance === null && inited) { //內部自動初始化得close
|
|
1446
1527
|
await closeSequelize('save')
|
|
1447
1528
|
}
|
|
1448
1529
|
|
|
@@ -1470,6 +1551,8 @@ function WOrmReladb(opt = {}) {
|
|
|
1470
1551
|
* 不中斷其餘筆數,以此與[主鍵未命中]之ok為1區辨;主鍵值與主鍵欄型別不符者(整數類主鍵欄給'012'等)亦同
|
|
1471
1552
|
* 註: 主鍵值依主鍵欄型別正規化後刪除(字串類主鍵欄之123即'123'),正規化值寫回該筆數據
|
|
1472
1553
|
* 註: 本函數不受opt.autoGenPk影響,於任一設定下皆不補值
|
|
1554
|
+
* 註: 主鍵含孤立代理碼元或NUL者(含new String包裝者;UTF-8存放之資料庫會把孤立代理碼元改寫為U+FFFD而刪到他筆)為該筆失敗,ok為0並附err(invalid id: 原因 at [data[索引].主鍵欄],不回顯值),
|
|
1555
|
+
* 不送查詢,同批其他筆照常;只檢主鍵,其他欄位不檢(第66項, R13);全部筆數皆不合格時不連線。其他無效主鍵之err亦不回顯主鍵值(invalid data[索引].主鍵欄…)
|
|
1473
1556
|
*
|
|
1474
1557
|
* @memberOf WOrmReladb
|
|
1475
1558
|
* @param {Object|Array} data 輸入數據物件或陣列,會查找各數據的opt.pk值,有存在者就刪除
|
|
@@ -1485,9 +1568,6 @@ function WOrmReladb(opt = {}) {
|
|
|
1485
1568
|
return []
|
|
1486
1569
|
}
|
|
1487
1570
|
|
|
1488
|
-
//cloneDeep
|
|
1489
|
-
data = cloneDeep(data)
|
|
1490
|
-
|
|
1491
1571
|
//instance
|
|
1492
1572
|
let instance = get(option, 'instance', null)
|
|
1493
1573
|
|
|
@@ -1508,23 +1588,32 @@ function WOrmReladb(opt = {}) {
|
|
|
1508
1588
|
//si
|
|
1509
1589
|
let si = instance
|
|
1510
1590
|
|
|
1591
|
+
//inited, 本次呼叫是否已進入initSequelize(僅此時finally才關閉sequelize; 預檢失敗於其前者不得關閉呼叫端先前持有之連線)
|
|
1592
|
+
let inited = false
|
|
1593
|
+
|
|
1511
1594
|
try {
|
|
1512
1595
|
|
|
1513
1596
|
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1514
1597
|
procClosed(instance)
|
|
1515
1598
|
|
|
1516
|
-
//
|
|
1517
|
-
|
|
1599
|
+
//chkWf, 主鍵之字串良構(孤立代理碼元、主鍵之NUL)逐筆檢核(R13): 先於連線、model載入與任何讀取、寫入、刪除, 檢查之物件即cloneDeep後之複本且其後只使用該複本;
|
|
1600
|
+
//不合格者為該筆失敗(ok為0附err, 同批其他筆照常), bads為逐筆訊息(合格者為null); 不隨autoGenPk改變
|
|
1601
|
+
let bads = chkWf(isarr(data) ? data : [data], opt.pk, false)
|
|
1602
|
+
let nOk = bads.filter((v) => v === null).length
|
|
1603
|
+
|
|
1604
|
+
//initSequelize, 內部自動初始化須在try內: 其例外(如驅動缺失)與si.err同屬整批性錯誤, 方能於reject前發出error事件, 已建構之sequelize亦由finally關閉; 全部不合格者無須連線
|
|
1605
|
+
if (instance === null && nOk > 0) {
|
|
1606
|
+
inited = true
|
|
1518
1607
|
si = await initSequelize()
|
|
1519
1608
|
}
|
|
1520
1609
|
|
|
1521
1610
|
//check
|
|
1522
|
-
if (si.err) {
|
|
1611
|
+
if (si !== null && si.err) {
|
|
1523
1612
|
throw si.err
|
|
1524
1613
|
}
|
|
1525
1614
|
|
|
1526
1615
|
//md
|
|
1527
|
-
let md = si.mds[opt.cl]
|
|
1616
|
+
let md = si === null ? null : si.mds[opt.cl]
|
|
1528
1617
|
|
|
1529
1618
|
//check
|
|
1530
1619
|
if (!isarr(data)) {
|
|
@@ -1532,11 +1621,23 @@ function WOrmReladb(opt = {}) {
|
|
|
1532
1621
|
}
|
|
1533
1622
|
|
|
1534
1623
|
//pmSeries
|
|
1535
|
-
res = await pmSeries(data, async(v) => {
|
|
1624
|
+
res = await pmSeries(data, async(v, k) => {
|
|
1536
1625
|
|
|
1537
1626
|
//rest
|
|
1538
1627
|
let rest = null
|
|
1539
1628
|
|
|
1629
|
+
//check, 主鍵字串不合格之筆為該筆失敗: 不送查詢, 同批其他筆照常
|
|
1630
|
+
if (bads[k] !== null) {
|
|
1631
|
+
rest = {
|
|
1632
|
+
n: 0,
|
|
1633
|
+
nDeleted: 0,
|
|
1634
|
+
ok: 0,
|
|
1635
|
+
err: bads[k],
|
|
1636
|
+
}
|
|
1637
|
+
emitError('del', [v], rest.err)
|
|
1638
|
+
return rest
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1540
1641
|
//pkv
|
|
1541
1642
|
let pkv = get(v, opt.pk)
|
|
1542
1643
|
|
|
@@ -1548,7 +1649,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1548
1649
|
n: 0,
|
|
1549
1650
|
nDeleted: 0,
|
|
1550
1651
|
ok: 0,
|
|
1551
|
-
err: `invalid ${opt.pk}
|
|
1652
|
+
err: `invalid data[${k}].${opt.pk}`,
|
|
1552
1653
|
}
|
|
1553
1654
|
|
|
1554
1655
|
//emit
|
|
@@ -1566,7 +1667,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1566
1667
|
n: 0,
|
|
1567
1668
|
nDeleted: 0,
|
|
1568
1669
|
ok: 0,
|
|
1569
|
-
err: `invalid ${opt.pk}
|
|
1670
|
+
err: `invalid data[${k}].${opt.pk}, type does not match pk column`,
|
|
1570
1671
|
}
|
|
1571
1672
|
|
|
1572
1673
|
//emit
|
|
@@ -1632,7 +1733,7 @@ function WOrmReladb(opt = {}) {
|
|
|
1632
1733
|
finally {
|
|
1633
1734
|
|
|
1634
1735
|
//closeSequelize
|
|
1635
|
-
if (instance === null) { //內部自動初始化得close
|
|
1736
|
+
if (instance === null && inited) { //內部自動初始化得close
|
|
1636
1737
|
await closeSequelize('del')
|
|
1637
1738
|
}
|
|
1638
1739
|
|
|
@@ -1889,6 +1990,24 @@ function WOrmReladb(opt = {}) {
|
|
|
1889
1990
|
// return closeSequelize('external')
|
|
1890
1991
|
// }
|
|
1891
1992
|
ee.genTransaction = genTransaction
|
|
1993
|
+
|
|
1994
|
+
|
|
1995
|
+
//snap, 數據於呼叫當下即同步複製(R13): useStable時各函數經佇列延後執行(pmQueue於執行前delay), 若於函數內才複製,
|
|
1996
|
+
//呼叫端於呼叫後立即改動原數據(如重用同一陣列)會影響實際檢核與寫入之值; 函數內只使用此複本, 檢核與寫入皆作用於同一份複本
|
|
1997
|
+
//複製拋錯(如getter拋錯)與函數內之其他錯誤同為rejected promise
|
|
1998
|
+
let snap = (fn) => {
|
|
1999
|
+
return function(data, ...rest) {
|
|
2000
|
+
let d
|
|
2001
|
+
try {
|
|
2002
|
+
d = cloneDeep(data)
|
|
2003
|
+
}
|
|
2004
|
+
catch (err) {
|
|
2005
|
+
return Promise.reject(err)
|
|
2006
|
+
}
|
|
2007
|
+
return fn(d, ...rest)
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1892
2011
|
if (opt.useStable) {
|
|
1893
2012
|
//用佇列(同時最大執行數1且先進先執行)處理高併發之情形
|
|
1894
2013
|
//若沒管控:
|
|
@@ -1900,18 +2019,18 @@ function WOrmReladb(opt = {}) {
|
|
|
1900
2019
|
ee.selectByPk = function() {
|
|
1901
2020
|
return pmq(selectByPk, ...arguments)
|
|
1902
2021
|
}
|
|
1903
|
-
ee.insert =
|
|
1904
|
-
return pmq(insert, ...
|
|
1905
|
-
}
|
|
1906
|
-
ee.insertBulk =
|
|
1907
|
-
return pmq(insertBulk, ...
|
|
1908
|
-
}
|
|
1909
|
-
ee.save =
|
|
1910
|
-
return pmq(save, ...
|
|
1911
|
-
}
|
|
1912
|
-
ee.del =
|
|
1913
|
-
return pmq(del, ...
|
|
1914
|
-
}
|
|
2022
|
+
ee.insert = snap((...args) => {
|
|
2023
|
+
return pmq(insert, ...args)
|
|
2024
|
+
})
|
|
2025
|
+
ee.insertBulk = snap((...args) => {
|
|
2026
|
+
return pmq(insertBulk, ...args)
|
|
2027
|
+
})
|
|
2028
|
+
ee.save = snap((...args) => {
|
|
2029
|
+
return pmq(save, ...args)
|
|
2030
|
+
})
|
|
2031
|
+
ee.del = snap((...args) => {
|
|
2032
|
+
return pmq(del, ...args)
|
|
2033
|
+
})
|
|
1915
2034
|
ee.delAll = function() {
|
|
1916
2035
|
return pmq(delAll, ...arguments)
|
|
1917
2036
|
}
|
|
@@ -1919,10 +2038,10 @@ function WOrmReladb(opt = {}) {
|
|
|
1919
2038
|
else {
|
|
1920
2039
|
ee.select = select
|
|
1921
2040
|
ee.selectByPk = selectByPk
|
|
1922
|
-
ee.insert = insert
|
|
1923
|
-
ee.insertBulk = insertBulk
|
|
1924
|
-
ee.save = save
|
|
1925
|
-
ee.del = del
|
|
2041
|
+
ee.insert = snap(insert)
|
|
2042
|
+
ee.insertBulk = snap(insertBulk)
|
|
2043
|
+
ee.save = snap(save)
|
|
2044
|
+
ee.del = snap(del)
|
|
1926
2045
|
ee.delAll = delAll
|
|
1927
2046
|
}
|
|
1928
2047
|
|