w-orm-reladb 1.0.47 → 1.0.49
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 +106 -50
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +802 -73
- package/docs/WOrmReladb.mjs.html +988 -298
- package/docs/index.html +2 -2
- package/g-genModelsByTabs.mjs +1 -1
- package/g-mssql-concurrency.mjs +1 -1
- package/g-mssql-timerQuery.mjs +3 -3
- package/g-mssql-transaction-commit.mjs +7 -7
- package/g-mssql-transaction-rollback.mjs +7 -7
- package/g-mssql.mjs +7 -7
- package/g-sqlite-concurrency.mjs +1 -1
- package/g-sqlite-encryption-transaction-commit.mjs +3 -3
- package/g-sqlite-encryption.mjs +7 -7
- package/g-sqlite-transaction-commit.mjs +7 -7
- package/g-sqlite-transaction-rollback.mjs +7 -7
- package/g-sqlite.mjs +7 -7
- package/package.json +1 -1
- package/script.txt +0 -1
- package/src/WOrmReladb.mjs +986 -296
- package/test/api-mssql.test.mjs +4 -201
- package/test/api-sqlite.test.mjs +12 -206
- package/test/api-suite.mjs +874 -0
- package/toolg/gDistRollup.mjs +1 -0
- package/worm.sqlite +0 -0
- package/test/api-fixtures.mjs +0 -48
package/src/WOrmReladb.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import events from 'events'
|
|
3
2
|
import Sequelize from 'sequelize'
|
|
4
3
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
|
5
4
|
import get from 'lodash-es/get.js'
|
|
@@ -7,13 +6,19 @@ import map from 'lodash-es/map.js'
|
|
|
7
6
|
import each from 'lodash-es/each.js'
|
|
8
7
|
import size from 'lodash-es/size.js'
|
|
9
8
|
import split from 'lodash-es/split.js'
|
|
9
|
+
import keys from 'lodash-es/keys.js'
|
|
10
|
+
import pick from 'lodash-es/pick.js'
|
|
11
|
+
import omit from 'lodash-es/omit.js'
|
|
12
|
+
import isEqual from 'lodash-es/isEqual.js'
|
|
13
|
+
import evem from 'wsemi/src/evem.mjs'
|
|
10
14
|
import genPm from 'wsemi/src/genPm.mjs'
|
|
11
|
-
import
|
|
15
|
+
import genIDSeq from 'wsemi/src/genIDSeq.mjs'
|
|
12
16
|
import isarr from 'wsemi/src/isarr.mjs'
|
|
13
17
|
import isearr from 'wsemi/src/isearr.mjs'
|
|
14
18
|
import isobj from 'wsemi/src/isobj.mjs'
|
|
15
19
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
16
20
|
import isbol from 'wsemi/src/isbol.mjs'
|
|
21
|
+
import isnum from 'wsemi/src/isnum.mjs'
|
|
17
22
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
18
23
|
import arrHas from 'wsemi/src/arrHas.mjs'
|
|
19
24
|
import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
@@ -44,7 +49,7 @@ let pmq = pmQueue(1)
|
|
|
44
49
|
* @param {String} [opt.modelType='js'] 輸入資料表設定檔類型字串,可有'js'、'json',預設'js'
|
|
45
50
|
* @param {Boolean} [opt.logging=false] 輸入是否輸出實際執行的sql指令布林值,預設false
|
|
46
51
|
* @param {String} [opt.pk='id'] 輸入數據主鍵字串,預設'id'
|
|
47
|
-
* @param {Boolean} [opt.
|
|
52
|
+
* @param {Boolean} [opt.autoGenPk=true] 輸入若數據pk(id)欄位沒給時則自動給予隨機uuid,型別為布林值,預設true
|
|
48
53
|
* @param {Boolean} [opt.useStable=true] 輸入是否使用穩定模式,使用佇列管理同時只能進行一種操作故會犧牲效能,sqlite需開啟穩定模式才不會有非預期錯誤,型別為布林值,預設true
|
|
49
54
|
* @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
|
|
50
55
|
*/
|
|
@@ -74,8 +79,8 @@ function WOrmReladb(opt = {}) {
|
|
|
74
79
|
if (!isestr(opt.pk)) {
|
|
75
80
|
opt.pk = 'id'
|
|
76
81
|
}
|
|
77
|
-
if (!isbol(opt.
|
|
78
|
-
opt.
|
|
82
|
+
if (!isbol(opt.autoGenPk)) {
|
|
83
|
+
opt.autoGenPk = true
|
|
79
84
|
}
|
|
80
85
|
if (!isbol(opt.useStable)) {
|
|
81
86
|
opt.useStable = true
|
|
@@ -93,8 +98,18 @@ function WOrmReladb(opt = {}) {
|
|
|
93
98
|
let Op = Sequelize.Op
|
|
94
99
|
|
|
95
100
|
|
|
96
|
-
//ee
|
|
97
|
-
|
|
101
|
+
//ee, 採wsemi之evem(即eventemitter3), 其於'error'無監聽者時僅回傳false而不拋出,
|
|
102
|
+
//故本套件之操作行為不因呼叫端有無註冊監聽而改變; Node內建之EventEmitter具該拋出語義, 不可用
|
|
103
|
+
let ee = evem()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
//opt.url解析失敗一律拋出, 因其屬呼叫端未履行契約, 且解析失敗後各函數皆無從運作,
|
|
107
|
+
//建構為同步而無從以Promise.reject回報, 拋出即為reject之同步對應;
|
|
108
|
+
//若僅console.log而回傳未綁定各函數之ee, 錯誤不經任何管道抵達呼叫端, 呼叫端僅得到
|
|
109
|
+
//[w.select is not a function]之無關訊息, 與本規格[錯誤不得靜默]之通則相違
|
|
110
|
+
let throwUrlErr = (msg) => {
|
|
111
|
+
throw new Error(`${msg}, opt.url[${opt.url}]`)
|
|
112
|
+
}
|
|
98
113
|
|
|
99
114
|
|
|
100
115
|
//dialect
|
|
@@ -102,12 +117,10 @@ function WOrmReladb(opt = {}) {
|
|
|
102
117
|
ss = split(opt.url, '://')
|
|
103
118
|
dialect = get(ss, 0, null)
|
|
104
119
|
if (!dialect) {
|
|
105
|
-
|
|
106
|
-
return ee
|
|
120
|
+
throwUrlErr('no dialect in opt.url')
|
|
107
121
|
}
|
|
108
122
|
if (!arrHas(dialect, ['mssql', 'sqlite', 'mysql', 'mariadb', 'postgres'])) {
|
|
109
|
-
|
|
110
|
-
return ee
|
|
123
|
+
throwUrlErr('invalid dialect in opt.url')
|
|
111
124
|
}
|
|
112
125
|
u = get(ss, 1, '') //另存給後面使用
|
|
113
126
|
|
|
@@ -120,19 +133,16 @@ function WOrmReladb(opt = {}) {
|
|
|
120
133
|
ss = get(ss, 0, '')
|
|
121
134
|
ss = split(ss, ':')
|
|
122
135
|
if (size(ss) !== 2) {
|
|
123
|
-
|
|
124
|
-
return ee
|
|
136
|
+
throwUrlErr('invalid username or password in opt.url')
|
|
125
137
|
}
|
|
126
138
|
username = get(ss, 0, '')
|
|
127
139
|
password = get(ss, 1, '')
|
|
128
140
|
if (dialect !== 'sqlite' || (dialect === 'sqlite' && opt.useEncryption)) {
|
|
129
141
|
if (username === '') {
|
|
130
|
-
|
|
131
|
-
return ee
|
|
142
|
+
throwUrlErr('invalid username in opt.url')
|
|
132
143
|
}
|
|
133
144
|
if (password === '') {
|
|
134
|
-
|
|
135
|
-
return ee
|
|
145
|
+
throwUrlErr('invalid password in opt.url')
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
148
|
|
|
@@ -141,21 +151,218 @@ function WOrmReladb(opt = {}) {
|
|
|
141
151
|
let host
|
|
142
152
|
let port
|
|
143
153
|
ss = split(u, ':')
|
|
144
|
-
// if (size(ss) !== 2) {
|
|
145
|
-
// console.log('invalid host or port in opt.url')
|
|
146
|
-
// return ee
|
|
147
|
-
// }
|
|
148
154
|
host = get(ss, 0, null)
|
|
149
155
|
port = get(ss, 1, null)
|
|
150
156
|
if (dialect !== 'sqlite') {
|
|
151
157
|
if (!host) {
|
|
152
|
-
|
|
153
|
-
return ee
|
|
158
|
+
throwUrlErr('invalid host in opt.url')
|
|
154
159
|
}
|
|
155
160
|
if (!port) {
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
throwUrlErr('invalid port in opt.url')
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 取錯誤訊息字串,供逐筆結果之err欄位與error事件使用
|
|
168
|
+
*
|
|
169
|
+
* @ignore
|
|
170
|
+
* @param {Error|String} err 輸入錯誤物件或字串
|
|
171
|
+
* @returns {String} 回傳錯誤訊息字串
|
|
172
|
+
*/
|
|
173
|
+
function getErrMsg(err) {
|
|
174
|
+
|
|
175
|
+
//message
|
|
176
|
+
let message = get(err, 'message')
|
|
177
|
+
if (isestr(message)) {
|
|
178
|
+
return message
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return String(err)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 發出change事件,訂閱函數拋錯不得影響本次操作之結果,故另包try並自行吞掉
|
|
187
|
+
*
|
|
188
|
+
* @ignore
|
|
189
|
+
* @param {String} mode 輸入操作別字串
|
|
190
|
+
* @param {Object|Array|null} data 輸入本次操作之數據
|
|
191
|
+
* @param {Object|Array} res 輸入本次操作之結果
|
|
192
|
+
* @returns {undefined} 無回傳值
|
|
193
|
+
*/
|
|
194
|
+
function emitChange(mode, data, res) {
|
|
195
|
+
try {
|
|
196
|
+
ee.emit('change', mode, data, res)
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.log(err)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 發出error事件,操作發生錯誤時發出,錯誤訊息一律轉為字串
|
|
206
|
+
* 註: 事件僅為附加通知,所送出之資訊必另有正規管道——整批性錯誤經Promise.reject,逐筆失敗經該筆之err欄位
|
|
207
|
+
* 註: 訂閱函數拋錯不得影響本次操作之結果,故另包try並自行吞掉
|
|
208
|
+
* 註: 正常結果不得發出本事件,如insert全數已存在、save合併後內容相同、del主鍵未命中、selectByPk查無數據
|
|
209
|
+
*
|
|
210
|
+
* @ignore
|
|
211
|
+
* @param {String} mode 輸入操作別字串
|
|
212
|
+
* @param {Object|Array|null} data 輸入本次操作之數據
|
|
213
|
+
* @param {Error|String} err 輸入錯誤物件或字串
|
|
214
|
+
* @returns {undefined} 無回傳值
|
|
215
|
+
*/
|
|
216
|
+
function emitError(mode, data, err) {
|
|
217
|
+
try {
|
|
218
|
+
ee.emit('error', mode, data, getErrMsg(err))
|
|
158
219
|
}
|
|
220
|
+
catch (errEmit) {
|
|
221
|
+
console.log(errEmit)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 判定主鍵值是否有效
|
|
228
|
+
* 註: select以外之五函數對主鍵之認定一律以本函數為準,令selectByPk之[命中]與insert、save、del內對既有數據之認定一致
|
|
229
|
+
*
|
|
230
|
+
* @ignore
|
|
231
|
+
* @param {*} v 輸入主鍵值
|
|
232
|
+
* @returns {Boolean} 回傳主鍵值是否有效布林值
|
|
233
|
+
*/
|
|
234
|
+
function isEffPk(v) {
|
|
235
|
+
|
|
236
|
+
//非空字串
|
|
237
|
+
if (isestr(v)) {
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//數值, 含0, 供整數型主鍵使用
|
|
242
|
+
if (isnum(v)) {
|
|
243
|
+
return true
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 判定是否為主鍵重複之錯誤
|
|
252
|
+
* 註: sequelize於mssql、sqlite、mysql、mariadb、postgres皆將唯一鍵衝突轉為UniqueConstraintError,故此判定與dialect無關
|
|
253
|
+
*
|
|
254
|
+
* @ignore
|
|
255
|
+
* @param {Error} err 輸入錯誤物件
|
|
256
|
+
* @returns {Boolean} 回傳是否為主鍵重複錯誤布林值
|
|
257
|
+
*/
|
|
258
|
+
function isDupPkError(err) {
|
|
259
|
+
return err instanceof Sequelize.UniqueConstraintError
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 檢查呼叫端所給之實例是否已關閉,已關閉者拋出令各函數於入口快速失敗
|
|
265
|
+
* 註: 實例已關閉為終態,後續操作必然失敗,屬影響全部數據之整批性錯誤,
|
|
266
|
+
* 故不得降為逐筆ok為0而整批resolve,亦不得以正常空結果(null、未命中、空陣列)回應——
|
|
267
|
+
* 後者使[已關閉]與[查無資料]同形,去重類呼叫端將把每筆判為未存在而靜默fail-open
|
|
268
|
+
* 註: closeSequelize於關閉後將sequelize設為null,故據此判定即為精確訊號,
|
|
269
|
+
* 不須倚賴驅動層之錯誤訊息比對(連線管理器關閉後所拋者為原生Error而非sequelize之錯誤類別)
|
|
270
|
+
* 註: 內部自行初始化者(instance為null)每次呼叫各自開關連線,不受本檢查影響
|
|
271
|
+
*
|
|
272
|
+
* @ignore
|
|
273
|
+
* @param {Object|null} instance 輸入呼叫端所給之實例物件
|
|
274
|
+
* @returns {undefined} 無回傳值
|
|
275
|
+
*/
|
|
276
|
+
function procClosed(instance) {
|
|
277
|
+
|
|
278
|
+
//check
|
|
279
|
+
if (instance === null) {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
//check
|
|
284
|
+
if (sequelize === null) {
|
|
285
|
+
throw new Error(`instance is closed, need to use init() to get a new instance`)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 判定錯誤是否影響後續所有筆數而應視為整批性錯誤
|
|
293
|
+
* 註: 依T4之判別原則,錯誤只影響該筆資料者為逐筆失敗,影響後續所有筆數者為整批性錯誤;
|
|
294
|
+
* 連線層錯誤屬後者,故於逐筆函數內攔得時須往外拋而不得降為該筆ok為0
|
|
295
|
+
* 註: 連線管理器關閉後所拋者為原生Error而非sequelize之ConnectionError,故另以訊息辨識
|
|
296
|
+
*
|
|
297
|
+
* @ignore
|
|
298
|
+
* @param {Error} err 輸入錯誤物件
|
|
299
|
+
* @returns {Boolean} 回傳是否為整批性錯誤布林值
|
|
300
|
+
*/
|
|
301
|
+
function isBatchLevelError(err) {
|
|
302
|
+
|
|
303
|
+
//check, sequelize之連線層錯誤家族
|
|
304
|
+
if (err instanceof Sequelize.ConnectionError) {
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//msg
|
|
309
|
+
let msg = getErrMsg(err)
|
|
310
|
+
|
|
311
|
+
//check
|
|
312
|
+
if (msg.indexOf('connection manager was closed') >= 0) {
|
|
313
|
+
return true
|
|
314
|
+
}
|
|
315
|
+
if (msg.indexOf('SQLITE_MISUSE') >= 0) {
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return false
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 檢查並補齊單筆數據之主鍵
|
|
325
|
+
* autoGenPk為true時未帶有效主鍵者自動產生,為false時往外拋
|
|
326
|
+
* 註: 未帶有效主鍵屬呼叫端未履行契約而非某一筆資料本身之問題,故為整批性錯誤而不降級為該筆ok為0,
|
|
327
|
+
* 若降級為逐筆結果,呼叫端易於整批resolve之下漏看,使[忘了給主鍵]靜默變成[少寫了幾筆]
|
|
328
|
+
* 註: 本函數須於任何寫入之前一次對全部數據完成,令拋錯時同批之有效筆數亦不會被寫入
|
|
329
|
+
* 註: 自動產生之主鍵值為字串,故補值前檢查主鍵欄位確為字串類型,不符者以明確訊息拋出,
|
|
330
|
+
* 而非任由資料庫回報型別錯誤; 本套件之model定義於手上得知主鍵型別,故不適用T6之[套件無從得知主鍵型別]例外
|
|
331
|
+
*
|
|
332
|
+
* @ignore
|
|
333
|
+
* @param {Object} md 輸入資料表model物件
|
|
334
|
+
* @param {Object} v 輸入數據物件
|
|
335
|
+
* @param {Number} k 輸入數據於陣列內之索引
|
|
336
|
+
* @returns {Object} 回傳補齊主鍵之數據物件
|
|
337
|
+
*/
|
|
338
|
+
function procPk(md, v, k) {
|
|
339
|
+
|
|
340
|
+
//check
|
|
341
|
+
if (isEffPk(get(v, opt.pk))) {
|
|
342
|
+
return v
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
//check, autoGenPk為false時主鍵須由呼叫端自備
|
|
346
|
+
if (!opt.autoGenPk) {
|
|
347
|
+
throw new Error(`invalid data[${k}].${opt.pk}, autoGenPk is false`)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
//at
|
|
351
|
+
let at = get(md, `rawAttributes.${opt.pk}`)
|
|
352
|
+
if (!iseobj(at)) {
|
|
353
|
+
throw new Error(`can not find pk column [${opt.pk}] in model [${opt.cl}]`)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
//check, 自動產生之主鍵值為字串, 主鍵欄位須為字串類型方能容納
|
|
357
|
+
let tp = get(at, 'type.key', '')
|
|
358
|
+
if (!arrHas(tp, ['STRING', 'TEXT', 'CHAR', 'UUID', 'CITEXT'])) {
|
|
359
|
+
throw new Error(`autoGenPk is true but pk column [${opt.pk}] in model [${opt.cl}] is [${tp}], auto generated pk is a string, need to set autoGenPk to false and supply pk by caller`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
//genIDSeq, 為UUIDv7格式而具單調遞增性, 主鍵接近順序遞增可減少索引頁面分裂
|
|
363
|
+
v[opt.pk] = genIDSeq()
|
|
364
|
+
|
|
365
|
+
return v
|
|
159
366
|
}
|
|
160
367
|
|
|
161
368
|
|
|
@@ -351,23 +558,27 @@ function WOrmReladb(opt = {}) {
|
|
|
351
558
|
}
|
|
352
559
|
}
|
|
353
560
|
|
|
561
|
+
//isErr, res
|
|
562
|
+
let isErr = false
|
|
563
|
+
let res = null
|
|
564
|
+
|
|
354
565
|
//si
|
|
355
566
|
let si = instance
|
|
356
567
|
if (instance === null) {
|
|
357
568
|
si = await initSequelize()
|
|
358
569
|
}
|
|
359
|
-
// else {
|
|
360
|
-
// console.log('select use instance', instance)
|
|
361
|
-
// }
|
|
362
570
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
571
|
+
try {
|
|
572
|
+
|
|
573
|
+
//check, 連線或匯入models失敗影響本次操作之全部數據, 屬整批性錯誤
|
|
574
|
+
if (si.err) {
|
|
575
|
+
throw si.err
|
|
576
|
+
}
|
|
366
577
|
|
|
367
578
|
//md
|
|
368
579
|
let md = si.mds[opt.cl]
|
|
369
580
|
|
|
370
|
-
//setting
|
|
581
|
+
//setting, raw令回傳純數據物件而不含資料庫內部欄位
|
|
371
582
|
let setting = {
|
|
372
583
|
where: useFind,
|
|
373
584
|
raw: true,
|
|
@@ -376,37 +587,178 @@ function WOrmReladb(opt = {}) {
|
|
|
376
587
|
setting.transaction = transaction
|
|
377
588
|
}
|
|
378
589
|
|
|
379
|
-
//findAll
|
|
380
|
-
|
|
590
|
+
//findAll, 恆回傳陣列, 無符合數據為空陣列
|
|
591
|
+
res = await md.findAll(setting)
|
|
592
|
+
if (!isarr(res)) {
|
|
593
|
+
res = []
|
|
594
|
+
}
|
|
381
595
|
|
|
382
596
|
}
|
|
383
|
-
|
|
384
|
-
|
|
597
|
+
catch (err) {
|
|
598
|
+
isErr = true
|
|
599
|
+
res = err
|
|
385
600
|
}
|
|
601
|
+
finally {
|
|
602
|
+
|
|
603
|
+
//closeSequelize
|
|
604
|
+
if (instance === null) { //內部自動初始化得close
|
|
605
|
+
await closeSequelize('select')
|
|
606
|
+
}
|
|
386
607
|
|
|
387
|
-
//closeSequelize
|
|
388
|
-
if (instance === null) { //內部自動初始化得close
|
|
389
|
-
await closeSequelize('select')
|
|
390
608
|
}
|
|
391
609
|
|
|
392
|
-
|
|
610
|
+
//check
|
|
611
|
+
if (isErr) {
|
|
612
|
+
emitError('select', null, res)
|
|
613
|
+
return Promise.reject(res)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return res
|
|
393
617
|
}
|
|
394
618
|
|
|
395
619
|
|
|
396
620
|
/**
|
|
397
|
-
*
|
|
621
|
+
* 由主鍵查詢單筆數據,因由資料庫依主鍵索引取值且僅回傳單筆,不需如select提取全部符合數據再處理,故數據量大時效能較佳
|
|
622
|
+
*
|
|
623
|
+
* 註: 主鍵欄位取自opt.pk,預設為id,支援由呼叫端指定
|
|
624
|
+
* 註: 主鍵未命中或主鍵值無效皆回傳null而不reject,[命中]之判定基準與insert、save、del內對既有數據之認定一致
|
|
625
|
+
* 註: 本函數不得有副作用,故不創建資料表
|
|
626
|
+
*
|
|
627
|
+
* @memberOf WOrmReladb
|
|
628
|
+
* @param {String|Number} pk 輸入主鍵值,即數據內opt.pk欄位之值
|
|
629
|
+
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
630
|
+
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
631
|
+
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
632
|
+
* @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵或主鍵值無效則回傳null,reject回傳錯誤訊息
|
|
633
|
+
*/
|
|
634
|
+
async function selectByPk(pk, option = {}) {
|
|
635
|
+
|
|
636
|
+
//instance
|
|
637
|
+
let instance = get(option, 'instance', null)
|
|
638
|
+
|
|
639
|
+
//transaction
|
|
640
|
+
let transaction = get(option, 'transaction', null)
|
|
641
|
+
|
|
642
|
+
//check, 實例已關閉之判定須先於[主鍵值無效回null], 否則兩者同形而使[已關閉]被誤讀為[查無資料]
|
|
643
|
+
try {
|
|
644
|
+
procClosed(instance)
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
emitError('selectByPk', null, err)
|
|
648
|
+
return Promise.reject(err)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
//check, 未給有效主鍵值視為查無數據, 不送查詢以免無效值被轉為null而誤中其他數據
|
|
652
|
+
if (!isEffPk(pk)) {
|
|
653
|
+
return null
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
//check
|
|
657
|
+
if (opt.useEncryption && dialect === 'sqlite') {
|
|
658
|
+
if (transaction !== null) {
|
|
659
|
+
console.log('@journeyapps/sqlcipher can not support transaction.')
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
//isErr, res
|
|
664
|
+
let isErr = false
|
|
665
|
+
let res = null
|
|
666
|
+
|
|
667
|
+
//si
|
|
668
|
+
let si = instance
|
|
669
|
+
if (instance === null) {
|
|
670
|
+
si = await initSequelize()
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
try {
|
|
674
|
+
|
|
675
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
676
|
+
procClosed(instance)
|
|
677
|
+
|
|
678
|
+
//check
|
|
679
|
+
if (si.err) {
|
|
680
|
+
throw si.err
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
//md
|
|
684
|
+
let md = si.mds[opt.cl]
|
|
685
|
+
|
|
686
|
+
//setting
|
|
687
|
+
let setting = {
|
|
688
|
+
where: { [opt.pk]: pk },
|
|
689
|
+
raw: true,
|
|
690
|
+
}
|
|
691
|
+
if (transaction !== null) {
|
|
692
|
+
setting.transaction = transaction
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
//findOne
|
|
696
|
+
let v = await md.findOne(setting)
|
|
697
|
+
|
|
698
|
+
//check, 判定基準與insert、save、del內對既有數據之認定一致
|
|
699
|
+
if (iseobj(v)) {
|
|
700
|
+
res = v
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
//不存在主鍵, 回傳null
|
|
704
|
+
res = null
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
}
|
|
708
|
+
catch (err) {
|
|
709
|
+
isErr = true
|
|
710
|
+
res = err
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
|
|
714
|
+
//closeSequelize
|
|
715
|
+
if (instance === null) { //內部自動初始化得close
|
|
716
|
+
await closeSequelize('selectByPk')
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
//check
|
|
722
|
+
if (isErr) {
|
|
723
|
+
emitError('selectByPk', null, res)
|
|
724
|
+
return Promise.reject(res)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return res
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
|
|
733
|
+
*
|
|
734
|
+
* 由資料庫於主鍵之唯一約束上原子完成[檢查主鍵不存在]與[寫入],併發時同一主鍵僅有一次成功
|
|
735
|
+
*
|
|
736
|
+
* 註: n為輸入筆數即本次嘗試插入之基準,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
|
|
737
|
+
* 註: 同批含重複主鍵時僅首筆計入nInserted,其餘視為已存在
|
|
738
|
+
* 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
|
|
739
|
+
* 註: option.returnList開啟時改回與輸入等長且保序之逐筆結果陣列[{n,nInserted,ok}],
|
|
740
|
+
* 聚合計數只答得出[有幾筆是新的]而答不出[是哪幾筆],而後者正是去重之產出物;
|
|
741
|
+
* 逐筆元素之n與ok恆為1,資訊由nInserted承載,不變式為
|
|
742
|
+
* 陣列長度等於輸入筆數且filter(v => v.nInserted === 1).length等於聚合模式之nInserted
|
|
398
743
|
*
|
|
399
744
|
* @memberOf WOrmReladb
|
|
400
745
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
401
746
|
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
402
747
|
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
403
748
|
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
404
|
-
* @
|
|
749
|
+
* @param {Boolean} [option.returnList=false] 輸入是否改回逐筆結果陣列布林值,預設false
|
|
750
|
+
* @returns {Promise} 回傳Promise,resolve回傳插入結果{n,nInserted,ok},或於returnList開啟時回傳[{n,nInserted,ok}],reject回傳錯誤訊息
|
|
405
751
|
*/
|
|
406
752
|
async function insert(data, option = {}) {
|
|
407
753
|
|
|
754
|
+
//returnList, 回傳形式之切換為靜態, 僅由本選項之取值決定, 不因數據內容或執行結果而變
|
|
755
|
+
let returnList = get(option, 'returnList', false) === true
|
|
756
|
+
|
|
408
757
|
//check
|
|
409
758
|
if (!iseobj(data) && !isearr(data)) {
|
|
759
|
+
if (returnList) {
|
|
760
|
+
return []
|
|
761
|
+
}
|
|
410
762
|
return {
|
|
411
763
|
n: 0,
|
|
412
764
|
nInserted: 0,
|
|
@@ -430,19 +782,25 @@ function WOrmReladb(opt = {}) {
|
|
|
430
782
|
//cloneDeep
|
|
431
783
|
data = cloneDeep(data)
|
|
432
784
|
|
|
433
|
-
//
|
|
434
|
-
let
|
|
785
|
+
//isErr, res
|
|
786
|
+
let isErr = false
|
|
787
|
+
let res = null
|
|
435
788
|
|
|
436
789
|
//si
|
|
437
790
|
let si = instance
|
|
438
791
|
if (instance === null) {
|
|
439
792
|
si = await initSequelize()
|
|
440
793
|
}
|
|
441
|
-
// else {
|
|
442
|
-
// console.log('insert use instance', instance)
|
|
443
|
-
// }
|
|
444
794
|
|
|
445
|
-
|
|
795
|
+
try {
|
|
796
|
+
|
|
797
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
798
|
+
procClosed(instance)
|
|
799
|
+
|
|
800
|
+
//check
|
|
801
|
+
if (si.err) {
|
|
802
|
+
throw si.err
|
|
803
|
+
}
|
|
446
804
|
|
|
447
805
|
//md
|
|
448
806
|
let md = si.mds[opt.cl]
|
|
@@ -452,18 +810,13 @@ function WOrmReladb(opt = {}) {
|
|
|
452
810
|
data = [data]
|
|
453
811
|
}
|
|
454
812
|
|
|
455
|
-
//
|
|
456
|
-
|
|
813
|
+
//procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
|
|
814
|
+
data = map(data, (v, k) => {
|
|
815
|
+
return procPk(md, v, k)
|
|
816
|
+
})
|
|
457
817
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
data = map(data, function(v) {
|
|
461
|
-
if (!v[opt.pk]) {
|
|
462
|
-
v[opt.pk] = genID()
|
|
463
|
-
}
|
|
464
|
-
return v
|
|
465
|
-
})
|
|
466
|
-
}
|
|
818
|
+
//nAll, n之基準為輸入筆數
|
|
819
|
+
let nAll = size(data)
|
|
467
820
|
|
|
468
821
|
//setting
|
|
469
822
|
let setting = { }
|
|
@@ -471,53 +824,112 @@ function WOrmReladb(opt = {}) {
|
|
|
471
824
|
setting.transaction = transaction
|
|
472
825
|
}
|
|
473
826
|
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
.
|
|
482
|
-
|
|
483
|
-
|
|
827
|
+
//create, 逐筆插入以取得精確之nInserted
|
|
828
|
+
//註: 不採bulkCreate配合ignoreDuplicates, 因mssql未宣告該能力而選項會被靜默忽略仍撞唯一約束,
|
|
829
|
+
//且bulkCreate無從回報實際插入筆數; 單筆INSERT本即由主鍵之唯一約束原子完成檢查與寫入
|
|
830
|
+
//註: bIns即逐筆判定, 與輸入等長且保序, 供returnList直接包裝而不須另行推導
|
|
831
|
+
let bInss = await pmSeries(data, async(v) => {
|
|
832
|
+
|
|
833
|
+
//bIns, 撞既有主鍵者視為已存在而跳過, 其餘錯誤影響全部數據須往外拋
|
|
834
|
+
let bIns = await md.create(v, setting)
|
|
835
|
+
.then(() => {
|
|
836
|
+
return true
|
|
837
|
+
})
|
|
838
|
+
.catch((err) => {
|
|
839
|
+
if (isDupPkError(err)) {
|
|
840
|
+
return false
|
|
841
|
+
}
|
|
842
|
+
throw err
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
return bIns
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
if (returnList) {
|
|
849
|
+
|
|
850
|
+
//res, 與輸入等長且保序之逐筆結果; 各筆之n恆為1(該筆主鍵或為命中既有或經插入而產生),
|
|
851
|
+
//ok恆為1(insert之任何錯誤皆屬整批性錯誤而reject), 資訊由nInserted承載
|
|
852
|
+
res = map(bInss, (bIns) => {
|
|
853
|
+
return {
|
|
854
|
+
n: 1,
|
|
855
|
+
nInserted: bIns ? 1 : 0,
|
|
856
|
+
ok: 1,
|
|
857
|
+
}
|
|
484
858
|
})
|
|
485
859
|
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
|
|
863
|
+
//res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
|
|
864
|
+
res = {
|
|
865
|
+
n: nAll,
|
|
866
|
+
nInserted: size(bInss.filter((v) => v === true)),
|
|
867
|
+
ok: 1,
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
}
|
|
871
|
+
|
|
486
872
|
}
|
|
487
|
-
|
|
488
|
-
|
|
873
|
+
catch (err) {
|
|
874
|
+
isErr = true
|
|
875
|
+
res = err
|
|
489
876
|
}
|
|
877
|
+
finally {
|
|
878
|
+
|
|
879
|
+
//closeSequelize
|
|
880
|
+
if (instance === null) { //內部自動初始化得close
|
|
881
|
+
await closeSequelize('insert')
|
|
882
|
+
}
|
|
490
883
|
|
|
491
|
-
//closeSequelize
|
|
492
|
-
if (instance === null) { //內部自動初始化得close
|
|
493
|
-
await closeSequelize('insert')
|
|
494
884
|
}
|
|
495
885
|
|
|
496
|
-
|
|
886
|
+
//check
|
|
887
|
+
if (isErr) {
|
|
888
|
+
emitError('insert', data, res)
|
|
889
|
+
return Promise.reject(res)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
//emit, 須於結果定案後發出
|
|
893
|
+
emitChange('insert', data, res)
|
|
894
|
+
|
|
895
|
+
return res
|
|
497
896
|
}
|
|
498
897
|
|
|
499
898
|
|
|
500
899
|
/**
|
|
501
|
-
*
|
|
900
|
+
* 批次插入數據,全批視為一個單位:全部插入成功,或一筆都不寫入
|
|
901
|
+
*
|
|
902
|
+
* 本函數非insert之加速版,兩者衝突政策不同:insert於主鍵已存在時跳過該筆且整批ok為1,
|
|
903
|
+
* 本函數則整批reject且不寫入任何一筆,同批含重複主鍵者亦視為衝突。
|
|
904
|
+
* 確無衝突時兩者之可觀察結果完全相同,差異僅於有衝突時顯現。
|
|
905
|
+
*
|
|
906
|
+
* 註: n為輸入筆數即本次嘗試插入之基準;nInserted於成功時恆等於n,雖無額外資訊仍保留,
|
|
907
|
+
* 令呼叫端得與insert共用同一段結果處理程式碼
|
|
908
|
+
* 註: 不提供逐筆結果,故不出現ok為0與err;需要逐筆處置者改用insert
|
|
909
|
+
* 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
|
|
910
|
+
* 註: [全有全無]為獨立於T7之額外要求——T7僅保證每筆之[檢查主鍵不存在]與[寫入]為原子,不保證整批。
|
|
911
|
+
* mssql因綁定參數數量上限會由驅動層自動拆為多語句送出,實測1000筆之批次於末筆撞主鍵時已有946筆落盤,
|
|
912
|
+
* 故一律以交易包覆並於失敗時回滾;呼叫端已給transaction時改以巢狀交易(SAVEPOINT)包覆,
|
|
913
|
+
* 令回滾範圍限於本次呼叫而不影響呼叫端交易內先前之寫入
|
|
502
914
|
*
|
|
503
915
|
* @memberOf WOrmReladb
|
|
504
916
|
* @param {Object|Array} data 輸入數據物件或陣列
|
|
505
917
|
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
506
918
|
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
507
919
|
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
508
|
-
* @
|
|
509
|
-
* @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
|
|
920
|
+
* @returns {Promise} 回傳Promise,resolve回傳插入結果{n,nInserted,ok},reject回傳錯誤訊息
|
|
510
921
|
*/
|
|
511
|
-
async function
|
|
922
|
+
async function insertBulk(data, option = {}) {
|
|
512
923
|
|
|
513
924
|
//check
|
|
514
925
|
if (!iseobj(data) && !isearr(data)) {
|
|
515
|
-
return
|
|
926
|
+
return {
|
|
927
|
+
n: 0,
|
|
928
|
+
nInserted: 0,
|
|
929
|
+
ok: 1,
|
|
930
|
+
}
|
|
516
931
|
}
|
|
517
932
|
|
|
518
|
-
//cloneDeep
|
|
519
|
-
data = cloneDeep(data)
|
|
520
|
-
|
|
521
933
|
//instance
|
|
522
934
|
let instance = get(option, 'instance', null)
|
|
523
935
|
|
|
@@ -531,22 +943,28 @@ function WOrmReladb(opt = {}) {
|
|
|
531
943
|
}
|
|
532
944
|
}
|
|
533
945
|
|
|
534
|
-
//
|
|
535
|
-
|
|
946
|
+
//cloneDeep
|
|
947
|
+
data = cloneDeep(data)
|
|
536
948
|
|
|
537
|
-
//
|
|
538
|
-
let
|
|
949
|
+
//isErr, res
|
|
950
|
+
let isErr = false
|
|
951
|
+
let res = null
|
|
539
952
|
|
|
540
953
|
//si
|
|
541
954
|
let si = instance
|
|
542
955
|
if (instance === null) {
|
|
543
956
|
si = await initSequelize()
|
|
544
957
|
}
|
|
545
|
-
// else {
|
|
546
|
-
// console.log('save use instance', instance)
|
|
547
|
-
// }
|
|
548
958
|
|
|
549
|
-
|
|
959
|
+
try {
|
|
960
|
+
|
|
961
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
962
|
+
procClosed(instance)
|
|
963
|
+
|
|
964
|
+
//check
|
|
965
|
+
if (si.err) {
|
|
966
|
+
throw si.err
|
|
967
|
+
}
|
|
550
968
|
|
|
551
969
|
//md
|
|
552
970
|
let md = si.mds[opt.cl]
|
|
@@ -556,157 +974,387 @@ function WOrmReladb(opt = {}) {
|
|
|
556
974
|
data = [data]
|
|
557
975
|
}
|
|
558
976
|
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
v[opt.pk] = genID()
|
|
564
|
-
}
|
|
565
|
-
return v
|
|
566
|
-
})
|
|
567
|
-
}
|
|
977
|
+
//procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批皆不會被寫入
|
|
978
|
+
data = map(data, (v, k) => {
|
|
979
|
+
return procPk(md, v, k)
|
|
980
|
+
})
|
|
568
981
|
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
// let tr = {}
|
|
572
|
-
// if (atomic) {
|
|
573
|
-
// t = await sequelize.transaction() //使用Unmanaged transaction (then-callback)
|
|
574
|
-
// tr = {
|
|
575
|
-
// transaction: t
|
|
576
|
-
// }
|
|
577
|
-
// }
|
|
982
|
+
//nAll, n之基準為輸入筆數
|
|
983
|
+
let nAll = size(data)
|
|
578
984
|
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
let pmm = genPm()
|
|
985
|
+
//useTx, sqlcipher不支援transaction, 該情形下改倚賴sqlite單一語句之原子性
|
|
986
|
+
let useTx = !(opt.useEncryption && dialect === 'sqlite')
|
|
582
987
|
|
|
583
|
-
|
|
584
|
-
|
|
988
|
+
//t, 呼叫端未給交易時自開; 已給時以巢狀交易(SAVEPOINT)包覆令回滾範圍限於本次呼叫
|
|
989
|
+
let t = null
|
|
990
|
+
if (useTx) {
|
|
991
|
+
t = (transaction !== null)
|
|
992
|
+
? await md.sequelize.transaction({ transaction })
|
|
993
|
+
: await md.sequelize.transaction()
|
|
994
|
+
}
|
|
585
995
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
996
|
+
//setting
|
|
997
|
+
let setting = {}
|
|
998
|
+
if (t !== null) {
|
|
999
|
+
setting.transaction = t
|
|
1000
|
+
}
|
|
1001
|
+
else if (transaction !== null) {
|
|
1002
|
+
setting.transaction = transaction
|
|
1003
|
+
}
|
|
590
1004
|
|
|
591
|
-
|
|
592
|
-
let setting = {
|
|
593
|
-
where: { [opt.pk]: v[opt.pk] },
|
|
594
|
-
raw: true,
|
|
595
|
-
}
|
|
596
|
-
if (transaction !== null) {
|
|
597
|
-
setting.transaction = transaction
|
|
598
|
-
}
|
|
1005
|
+
try {
|
|
599
1006
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
.catch((error) => {
|
|
603
|
-
ee.emit('error', error)
|
|
604
|
-
err = error
|
|
605
|
-
})
|
|
1007
|
+
//bulkCreate, 未加任何跳過選項, 故任一筆撞主鍵即整句失敗, 同批重複主鍵亦於此被偵測為衝突
|
|
1008
|
+
await md.bulkCreate(data, setting)
|
|
606
1009
|
|
|
1010
|
+
if (t !== null) {
|
|
1011
|
+
await t.commit()
|
|
607
1012
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
1013
|
+
|
|
1014
|
+
}
|
|
1015
|
+
catch (err) {
|
|
1016
|
+
if (t !== null) {
|
|
1017
|
+
await t.rollback().catch(() => {})
|
|
611
1018
|
}
|
|
1019
|
+
throw err
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
//res, 全有全無故成功時nInserted恆等於n
|
|
1023
|
+
res = {
|
|
1024
|
+
n: nAll,
|
|
1025
|
+
nInserted: nAll,
|
|
1026
|
+
ok: 1,
|
|
1027
|
+
}
|
|
612
1028
|
|
|
613
|
-
|
|
614
|
-
|
|
1029
|
+
}
|
|
1030
|
+
catch (err) {
|
|
1031
|
+
isErr = true
|
|
1032
|
+
res = err
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
615
1035
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
1036
|
+
//closeSequelize
|
|
1037
|
+
if (instance === null) { //內部自動初始化得close
|
|
1038
|
+
await closeSequelize('insertBulk')
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
}
|
|
623
1042
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1043
|
+
//check
|
|
1044
|
+
if (isErr) {
|
|
1045
|
+
emitError('insertBulk', data, res)
|
|
1046
|
+
return Promise.reject(res)
|
|
1047
|
+
}
|
|
629
1048
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
pmm.resolve({ n: 1, nModified: 1, ok: 1 })
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
//console.log('update 沒有更新資料', err)
|
|
636
|
-
pmm.resolve({ n: 1, nModified: 0, ok: 0 })
|
|
637
|
-
}
|
|
1049
|
+
//emit, 須於結果定案後發出
|
|
1050
|
+
emitChange('insertBulk', data, res)
|
|
638
1051
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
//沒有找到資料
|
|
1052
|
+
return res
|
|
1053
|
+
}
|
|
642
1054
|
|
|
643
|
-
//autoInsert
|
|
644
|
-
if (autoInsert) {
|
|
645
1055
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1056
|
+
/**
|
|
1057
|
+
* 儲存單筆數據
|
|
1058
|
+
*
|
|
1059
|
+
* 採條件寫入配合衝突偵測與重試: 每次寫入本身為單一條件式原子語句——插入由主鍵之唯一約束判定、
|
|
1060
|
+
* 更新由WHERE主鍵之比對判定,皆非由預讀值決定其成敗;預讀僅用於選擇發出哪一條語句,
|
|
1061
|
+
* 選錯不會產生錯誤結果,只會被偵測並重試(插入撞既有主鍵、更新未命中任何列),故縱使預讀值已過期,
|
|
1062
|
+
* 最終結果仍與該次操作單獨執行時相同。
|
|
1063
|
+
* 註: 不採sequelize之upsert,因sqlite無從於單一語句內回報本次係插入或更新(其created恆為null),
|
|
1064
|
+
* 而insert與save之規格要求nInserted精確,兩者於sqlite互斥,故改採本形式
|
|
1065
|
+
* 註: [內容相同]之判定基準為將待寫入物件合併進現值後與現值相同,相同則不寫入而nModified為0,
|
|
1066
|
+
* 合併取淺層以與後端整欄取代之寫入行為一致,令nModified忠實反映資料庫端是否真的寫入
|
|
1067
|
+
*
|
|
1068
|
+
* @ignore
|
|
1069
|
+
* @param {Object} md 輸入資料表model物件
|
|
1070
|
+
* @param {Object} v 輸入數據物件
|
|
1071
|
+
* @param {Boolean} autoInsert 輸入是否於查無數據時自動改以插入處理布林值
|
|
1072
|
+
* @param {Object|null} transaction 輸入交易(transaction)物件
|
|
1073
|
+
* @param {Array} ks 輸入model之欄位名陣列
|
|
1074
|
+
* @returns {Promise} 回傳Promise,resolve回傳本筆儲存結果,本筆失敗時亦resolve並以ok為0回報
|
|
1075
|
+
*/
|
|
1076
|
+
async function saveOne(md, v, autoInsert, transaction, ks) {
|
|
1077
|
+
|
|
1078
|
+
//nTry, 預測錯誤時重試, 給予有限次數令其收斂
|
|
1079
|
+
let nTry = 3
|
|
1080
|
+
|
|
1081
|
+
//rest
|
|
1082
|
+
let rest = null
|
|
1083
|
+
|
|
1084
|
+
//inserted, 供結果定案後發出insert事件用
|
|
1085
|
+
let inserted = false
|
|
1086
|
+
|
|
1087
|
+
//pkv
|
|
1088
|
+
let pkv = get(v, opt.pk)
|
|
1089
|
+
|
|
1090
|
+
//setting
|
|
1091
|
+
let settingRead = {
|
|
1092
|
+
where: { [opt.pk]: pkv },
|
|
1093
|
+
raw: true,
|
|
1094
|
+
}
|
|
1095
|
+
let settingUpd = {
|
|
1096
|
+
where: { [opt.pk]: pkv },
|
|
1097
|
+
}
|
|
1098
|
+
let settingIns = {}
|
|
1099
|
+
if (transaction !== null) {
|
|
1100
|
+
settingRead.transaction = transaction
|
|
1101
|
+
settingUpd.transaction = transaction
|
|
1102
|
+
settingIns.transaction = transaction
|
|
1103
|
+
}
|
|
651
1104
|
|
|
652
|
-
|
|
653
|
-
let rr = await md.create(v, setting)
|
|
654
|
-
.catch((error) => {
|
|
655
|
-
ee.emit('error', error)
|
|
656
|
-
err = error
|
|
657
|
-
})
|
|
1105
|
+
for (let i = 0; i < nTry; i++) {
|
|
658
1106
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1107
|
+
try {
|
|
1108
|
+
|
|
1109
|
+
//預讀, 供判斷內容是否相同之快速路徑, 其結果不用於決定寫入內容
|
|
1110
|
+
let _v = await md.findOne(settingRead)
|
|
1111
|
+
|
|
1112
|
+
if (iseobj(_v)) {
|
|
1113
|
+
//主鍵存在
|
|
1114
|
+
|
|
1115
|
+
//_vt, vt, 以待寫入物件之非主鍵欄位淺層覆蓋現值後與現值比對
|
|
1116
|
+
let _vt = omit(_v, opt.pk)
|
|
1117
|
+
let vt = {
|
|
1118
|
+
..._vt,
|
|
1119
|
+
...pick(omit(v, opt.pk), ks),
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
//check, 內容相同者不寫入
|
|
1123
|
+
if (isEqual(vt, _vt)) {
|
|
1124
|
+
rest = {
|
|
1125
|
+
n: 1,
|
|
1126
|
+
nInserted: 0,
|
|
1127
|
+
nModified: 0,
|
|
1128
|
+
ok: 1,
|
|
662
1129
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1130
|
+
break
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
//update, 單一UPDATE語句由WHERE主鍵原子完成查找與更新
|
|
1134
|
+
let rr = await md.update(v, settingUpd)
|
|
1135
|
+
let nAff = get(rr, 0, 0)
|
|
1136
|
+
|
|
1137
|
+
if (nAff > 0) {
|
|
1138
|
+
rest = {
|
|
1139
|
+
n: 1,
|
|
1140
|
+
nInserted: 0,
|
|
1141
|
+
nModified: 1,
|
|
1142
|
+
ok: 1,
|
|
666
1143
|
}
|
|
1144
|
+
break
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
//該列已被他方刪除, 重試改走插入
|
|
1148
|
+
continue
|
|
1149
|
+
}
|
|
667
1150
|
|
|
1151
|
+
//主鍵不存在且未開啟autoInsert則不寫入以免無中生有
|
|
1152
|
+
if (!autoInsert) {
|
|
1153
|
+
rest = {
|
|
1154
|
+
n: 0,
|
|
1155
|
+
nInserted: 0,
|
|
1156
|
+
nModified: 0,
|
|
1157
|
+
ok: 1,
|
|
668
1158
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1159
|
+
break
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
//create, 單一INSERT語句由主鍵之唯一約束原子完成檢查與寫入
|
|
1163
|
+
let bIns = await md.create(v, settingIns)
|
|
1164
|
+
.then(() => {
|
|
1165
|
+
return true
|
|
1166
|
+
})
|
|
1167
|
+
.catch((err) => {
|
|
1168
|
+
if (isDupPkError(err)) {
|
|
1169
|
+
return false
|
|
1170
|
+
}
|
|
1171
|
+
throw err
|
|
1172
|
+
})
|
|
1173
|
+
|
|
1174
|
+
if (bIns) {
|
|
1175
|
+
rest = {
|
|
1176
|
+
n: 1,
|
|
1177
|
+
nInserted: 1,
|
|
1178
|
+
nModified: 0,
|
|
1179
|
+
ok: 1,
|
|
672
1180
|
}
|
|
1181
|
+
inserted = true
|
|
1182
|
+
break
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
//他方已插入同一主鍵, 重試改走更新
|
|
1186
|
+
continue
|
|
1187
|
+
}
|
|
1188
|
+
catch (err) {
|
|
673
1189
|
|
|
1190
|
+
//check, 連線層錯誤影響後續所有筆數, 依T4屬整批性錯誤, 往外拋而不降為本筆ok為0
|
|
1191
|
+
if (isBatchLevelError(err)) {
|
|
1192
|
+
throw err
|
|
674
1193
|
}
|
|
675
1194
|
|
|
676
|
-
|
|
677
|
-
|
|
1195
|
+
//其餘錯誤視為本筆失敗, 不中斷整批, 由呼叫端以ok與err判讀
|
|
1196
|
+
rest = {
|
|
1197
|
+
n: 1,
|
|
1198
|
+
nInserted: 0,
|
|
1199
|
+
nModified: 0,
|
|
1200
|
+
ok: 0,
|
|
1201
|
+
err: getErrMsg(err),
|
|
1202
|
+
}
|
|
1203
|
+
break
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
//check, 重試耗盡仍未定案
|
|
1209
|
+
if (rest === null) {
|
|
1210
|
+
rest = {
|
|
1211
|
+
n: 1,
|
|
1212
|
+
nInserted: 0,
|
|
1213
|
+
nModified: 0,
|
|
1214
|
+
ok: 0,
|
|
1215
|
+
err: `can not save data by ${opt.pk}[${pkv}] in ${nTry} tries`,
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
//emit, 須於本筆結果定案後發出, 避免訂閱函數拋錯影響本筆結果
|
|
1220
|
+
if (inserted) {
|
|
1221
|
+
emitChange('insert', [v], rest)
|
|
1222
|
+
}
|
|
1223
|
+
if (rest.ok === 0) {
|
|
1224
|
+
emitError('save', [v], rest.err)
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return rest
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* 儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且option.autoInsert為true(預設)時改為插入
|
|
1233
|
+
*
|
|
1234
|
+
* 註: 回傳陣列恆與輸入等長,輸入單一物件亦回傳長度1之陣列
|
|
1235
|
+
* 註: n為主鍵命中筆數,值為0或1,命中(不論內容有無變更)或經插入而產生皆為1;nInserted與nModified恆同時出現
|
|
1236
|
+
* 註: [內容相同]之判定基準為將待寫入物件合併進現值後與現值相同,非待寫入物件與現值全等,
|
|
1237
|
+
* 故只給部份欄位且該些欄位值皆與現值相同時,合併結果等於現值,nModified為0
|
|
1238
|
+
* 註: 本筆失敗不中斷整批,該筆以ok為0並附err回報
|
|
1239
|
+
* 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
|
|
1240
|
+
*
|
|
1241
|
+
* @memberOf WOrmReladb
|
|
1242
|
+
* @param {Object|Array} data 輸入數據物件或陣列
|
|
1243
|
+
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
1244
|
+
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
1245
|
+
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
1246
|
+
* @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
|
|
1247
|
+
* @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列[{n,nInserted,nModified,ok}],reject回傳錯誤訊息
|
|
1248
|
+
*/
|
|
1249
|
+
async function save(data, option = {}) {
|
|
1250
|
+
|
|
1251
|
+
//check
|
|
1252
|
+
if (!iseobj(data) && !isearr(data)) {
|
|
1253
|
+
return []
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
//cloneDeep
|
|
1257
|
+
data = cloneDeep(data)
|
|
1258
|
+
|
|
1259
|
+
//instance
|
|
1260
|
+
let instance = get(option, 'instance', null)
|
|
1261
|
+
|
|
1262
|
+
//transaction
|
|
1263
|
+
let transaction = get(option, 'transaction', null)
|
|
1264
|
+
|
|
1265
|
+
//check
|
|
1266
|
+
if (opt.useEncryption && dialect === 'sqlite') {
|
|
1267
|
+
if (transaction !== null) {
|
|
1268
|
+
console.log('@journeyapps/sqlcipher can not support transaction.')
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
//autoInsert
|
|
1273
|
+
let autoInsert = get(option, 'autoInsert', true)
|
|
1274
|
+
|
|
1275
|
+
//isErr, res
|
|
1276
|
+
let isErr = false
|
|
1277
|
+
let res = null
|
|
1278
|
+
|
|
1279
|
+
//si
|
|
1280
|
+
let si = instance
|
|
1281
|
+
if (instance === null) {
|
|
1282
|
+
si = await initSequelize()
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
try {
|
|
1286
|
+
|
|
1287
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1288
|
+
procClosed(instance)
|
|
1289
|
+
|
|
1290
|
+
//check
|
|
1291
|
+
if (si.err) {
|
|
1292
|
+
throw si.err
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
//md
|
|
1296
|
+
let md = si.mds[opt.cl]
|
|
1297
|
+
|
|
1298
|
+
//check
|
|
1299
|
+
if (!isarr(data)) {
|
|
1300
|
+
data = [data]
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
//procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
|
|
1304
|
+
data = map(data, (v, k) => {
|
|
1305
|
+
return procPk(md, v, k)
|
|
1306
|
+
})
|
|
1307
|
+
|
|
1308
|
+
//ks, model之欄位名, 供合併後比對時濾除非欄位之鍵
|
|
1309
|
+
let ks = keys(get(md, 'rawAttributes', {}))
|
|
1310
|
+
|
|
1311
|
+
//pmSeries
|
|
1312
|
+
res = await pmSeries(data, async(v) => {
|
|
1313
|
+
return saveOne(md, v, autoInsert, transaction, ks)
|
|
678
1314
|
})
|
|
679
|
-
.then((res) => {
|
|
680
|
-
pm.resolve(res)
|
|
681
|
-
ee.emit('change', 'save', data, res)
|
|
682
|
-
})
|
|
683
|
-
.catch((error) => {
|
|
684
|
-
pm.reject(error)
|
|
685
|
-
})
|
|
686
1315
|
|
|
687
1316
|
}
|
|
688
|
-
|
|
689
|
-
|
|
1317
|
+
catch (err) {
|
|
1318
|
+
isErr = true
|
|
1319
|
+
res = err
|
|
690
1320
|
}
|
|
1321
|
+
finally {
|
|
1322
|
+
|
|
1323
|
+
//closeSequelize
|
|
1324
|
+
if (instance === null) { //內部自動初始化得close
|
|
1325
|
+
await closeSequelize('save')
|
|
1326
|
+
}
|
|
691
1327
|
|
|
692
|
-
//closeSequelize
|
|
693
|
-
if (instance === null) { //內部自動初始化得close
|
|
694
|
-
await closeSequelize('save')
|
|
695
1328
|
}
|
|
696
1329
|
|
|
697
|
-
|
|
1330
|
+
//check
|
|
1331
|
+
if (isErr) {
|
|
1332
|
+
emitError('save', data, res)
|
|
1333
|
+
return Promise.reject(res)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
//emit, 須於結果定案後發出
|
|
1337
|
+
emitChange('save', data, res)
|
|
1338
|
+
|
|
1339
|
+
return res
|
|
698
1340
|
}
|
|
699
1341
|
|
|
700
1342
|
|
|
701
1343
|
/**
|
|
702
1344
|
* 刪除數據
|
|
703
1345
|
*
|
|
1346
|
+
* 註: 回傳陣列恆與輸入等長,各筆之n與nDeleted皆為主鍵命中筆數,值為0或1,未命中為0且屬正常結果
|
|
1347
|
+
* 註: 判斷某筆是否真的被刪除一律以nDeleted為準
|
|
1348
|
+
* 註: 未帶有效主鍵者為該筆之輸入問題,回ok為0與err且不送查詢條件(以免無效值被轉為null而誤中其他數據),
|
|
1349
|
+
* 不中斷其餘筆數,以此與[主鍵未命中]之ok為1區辨
|
|
1350
|
+
* 註: 本函數不受opt.autoGenPk影響,於任一設定下皆不補值
|
|
1351
|
+
*
|
|
704
1352
|
* @memberOf WOrmReladb
|
|
705
1353
|
* @param {Object|Array} data 輸入數據物件或陣列,會查找各數據的opt.pk值,有存在者就刪除
|
|
706
1354
|
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
707
1355
|
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
708
1356
|
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
709
|
-
* @returns {Promise} 回傳Promise,resolve
|
|
1357
|
+
* @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列[{n,nDeleted,ok}],reject回傳錯誤訊息
|
|
710
1358
|
*/
|
|
711
1359
|
async function del(data, option = {}) {
|
|
712
1360
|
|
|
@@ -731,19 +1379,25 @@ function WOrmReladb(opt = {}) {
|
|
|
731
1379
|
}
|
|
732
1380
|
}
|
|
733
1381
|
|
|
734
|
-
//
|
|
735
|
-
let
|
|
1382
|
+
//isErr, res
|
|
1383
|
+
let isErr = false
|
|
1384
|
+
let res = null
|
|
736
1385
|
|
|
737
1386
|
//si
|
|
738
1387
|
let si = instance
|
|
739
1388
|
if (instance === null) {
|
|
740
1389
|
si = await initSequelize()
|
|
741
1390
|
}
|
|
742
|
-
// else {
|
|
743
|
-
// console.log('del use instance', instance)
|
|
744
|
-
// }
|
|
745
1391
|
|
|
746
|
-
|
|
1392
|
+
try {
|
|
1393
|
+
|
|
1394
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1395
|
+
procClosed(instance)
|
|
1396
|
+
|
|
1397
|
+
//check
|
|
1398
|
+
if (si.err) {
|
|
1399
|
+
throw si.err
|
|
1400
|
+
}
|
|
747
1401
|
|
|
748
1402
|
//md
|
|
749
1403
|
let md = si.mds[opt.cl]
|
|
@@ -754,106 +1408,116 @@ function WOrmReladb(opt = {}) {
|
|
|
754
1408
|
}
|
|
755
1409
|
|
|
756
1410
|
//pmSeries
|
|
757
|
-
await pmSeries(data, async
|
|
758
|
-
let pmm = genPm()
|
|
1411
|
+
res = await pmSeries(data, async(v) => {
|
|
759
1412
|
|
|
760
|
-
//
|
|
761
|
-
let
|
|
1413
|
+
//rest
|
|
1414
|
+
let rest = null
|
|
762
1415
|
|
|
763
|
-
//
|
|
764
|
-
let
|
|
765
|
-
if (v[opt.pk]) {
|
|
766
|
-
//有id
|
|
1416
|
+
//pkv
|
|
1417
|
+
let pkv = get(v, opt.pk)
|
|
767
1418
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1419
|
+
//check, 未帶有效主鍵者不補值亦不送查詢, 直接視為該筆無法處理
|
|
1420
|
+
if (!isEffPk(pkv)) {
|
|
1421
|
+
|
|
1422
|
+
//rest
|
|
1423
|
+
rest = {
|
|
1424
|
+
n: 0,
|
|
1425
|
+
nDeleted: 0,
|
|
1426
|
+
ok: 0,
|
|
1427
|
+
err: `invalid ${opt.pk}[${pkv}]`,
|
|
775
1428
|
}
|
|
776
1429
|
|
|
777
|
-
//
|
|
778
|
-
|
|
779
|
-
.catch((error) => {
|
|
780
|
-
ee.emit('error', error)
|
|
781
|
-
err = error
|
|
782
|
-
})
|
|
1430
|
+
//emit
|
|
1431
|
+
emitError('del', [v], rest.err)
|
|
783
1432
|
|
|
1433
|
+
return rest
|
|
784
1434
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1435
|
+
|
|
1436
|
+
//setting
|
|
1437
|
+
let setting = {
|
|
1438
|
+
where: { [opt.pk]: pkv },
|
|
1439
|
+
}
|
|
1440
|
+
if (transaction !== null) {
|
|
1441
|
+
setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
|
|
788
1442
|
}
|
|
789
1443
|
|
|
790
|
-
|
|
791
|
-
|
|
1444
|
+
//destroy, 單一DELETE語句即可取得命中筆數, 不另行findOne以免多一次往返且與刪除間存在競態
|
|
1445
|
+
await md.destroy(setting)
|
|
1446
|
+
.then((r) => {
|
|
792
1447
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
where: { [opt.pk]: v[opt.pk] },
|
|
796
|
-
}
|
|
797
|
-
if (transaction !== null) {
|
|
798
|
-
setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
|
|
799
|
-
}
|
|
1448
|
+
//nDel, n與nDeleted皆為主鍵命中筆數, 未命中為0且屬正常結果
|
|
1449
|
+
let nDel = isnum(r) ? r : 0
|
|
800
1450
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
1451
|
+
//rest
|
|
1452
|
+
rest = {
|
|
1453
|
+
n: nDel,
|
|
1454
|
+
nDeleted: nDel,
|
|
1455
|
+
ok: 1,
|
|
1456
|
+
}
|
|
807
1457
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
pmm.resolve({ n: 1, nDeleted: 1, ok: 1 })
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
//console.log('destroy 沒有刪除資料', err)
|
|
814
|
-
pmm.resolve({ n: 1, nDeleted: 0, ok: 0 })
|
|
815
|
-
}
|
|
1458
|
+
})
|
|
1459
|
+
.catch((err) => {
|
|
816
1460
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1461
|
+
//check, 連線層錯誤影響後續所有筆數, 依T4屬整批性錯誤, 往外拋而不降為本筆ok為0
|
|
1462
|
+
if (isBatchLevelError(err)) {
|
|
1463
|
+
throw err
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
//rest, 本筆失敗不中斷整批
|
|
1467
|
+
rest = {
|
|
1468
|
+
n: 1,
|
|
1469
|
+
nDeleted: 0,
|
|
1470
|
+
ok: 0,
|
|
1471
|
+
err: getErrMsg(err),
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
//emit
|
|
1475
|
+
emitError('del', [v], rest.err)
|
|
822
1476
|
|
|
823
|
-
|
|
824
|
-
|
|
1477
|
+
})
|
|
1478
|
+
|
|
1479
|
+
return rest
|
|
825
1480
|
})
|
|
826
|
-
.then((res) => {
|
|
827
|
-
pm.resolve(res)
|
|
828
|
-
ee.emit('change', 'del', data, res)
|
|
829
|
-
})
|
|
830
|
-
.catch((res) => {
|
|
831
|
-
pm.reject(res)
|
|
832
|
-
})
|
|
833
1481
|
|
|
834
1482
|
}
|
|
835
|
-
|
|
836
|
-
|
|
1483
|
+
catch (err) {
|
|
1484
|
+
isErr = true
|
|
1485
|
+
res = err
|
|
837
1486
|
}
|
|
1487
|
+
finally {
|
|
1488
|
+
|
|
1489
|
+
//closeSequelize
|
|
1490
|
+
if (instance === null) { //內部自動初始化得close
|
|
1491
|
+
await closeSequelize('del')
|
|
1492
|
+
}
|
|
838
1493
|
|
|
839
|
-
//closeSequelize
|
|
840
|
-
if (instance === null) { //內部自動初始化得close
|
|
841
|
-
await closeSequelize('del')
|
|
842
1494
|
}
|
|
843
1495
|
|
|
844
|
-
|
|
1496
|
+
//check
|
|
1497
|
+
if (isErr) {
|
|
1498
|
+
emitError('del', data, res)
|
|
1499
|
+
return Promise.reject(res)
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
//emit, 須於結果定案後發出
|
|
1503
|
+
emitChange('del', data, res)
|
|
1504
|
+
|
|
1505
|
+
return res
|
|
845
1506
|
}
|
|
846
1507
|
|
|
847
1508
|
|
|
848
1509
|
/**
|
|
849
|
-
*
|
|
1510
|
+
* 依條件刪除多筆數據,需與del分開,避免未傳數據導致直接刪除全表
|
|
1511
|
+
*
|
|
1512
|
+
* 註: n與nDeleted皆為實際刪除筆數,兩者恆相等,n不得取全表筆數
|
|
1513
|
+
* 註: find未給或為空物件時刪除全部數據;條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤
|
|
850
1514
|
*
|
|
851
1515
|
* @memberOf WOrmReladb
|
|
852
1516
|
* @param {Object} [find={}] 輸入刪除條件物件,不給予find則代表刪除全部數據
|
|
853
1517
|
* @param {Object} [option={}] 輸入設定物件,預設為{}
|
|
854
1518
|
* @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
|
|
855
1519
|
* @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
|
|
856
|
-
* @returns {Promise} 回傳Promise,resolve
|
|
1520
|
+
* @returns {Promise} 回傳Promise,resolve回傳刪除結果{n,nDeleted,ok},reject回傳錯誤訊息
|
|
857
1521
|
*/
|
|
858
1522
|
async function delAll(find = {}, option = {}) {
|
|
859
1523
|
|
|
@@ -876,19 +1540,25 @@ function WOrmReladb(opt = {}) {
|
|
|
876
1540
|
}
|
|
877
1541
|
}
|
|
878
1542
|
|
|
879
|
-
//
|
|
880
|
-
let
|
|
1543
|
+
//isErr, res
|
|
1544
|
+
let isErr = false
|
|
1545
|
+
let res = null
|
|
881
1546
|
|
|
882
1547
|
//si
|
|
883
1548
|
let si = instance
|
|
884
1549
|
if (instance === null) {
|
|
885
1550
|
si = await initSequelize()
|
|
886
1551
|
}
|
|
887
|
-
// else {
|
|
888
|
-
// console.log('delAll use instance', instance)
|
|
889
|
-
// }
|
|
890
1552
|
|
|
891
|
-
|
|
1553
|
+
try {
|
|
1554
|
+
|
|
1555
|
+
//check, 實例已關閉屬整批性錯誤, 於入口快速失敗
|
|
1556
|
+
procClosed(instance)
|
|
1557
|
+
|
|
1558
|
+
//check
|
|
1559
|
+
if (si.err) {
|
|
1560
|
+
throw si.err
|
|
1561
|
+
}
|
|
892
1562
|
|
|
893
1563
|
//md
|
|
894
1564
|
let md = si.mds[opt.cl]
|
|
@@ -901,29 +1571,41 @@ function WOrmReladb(opt = {}) {
|
|
|
901
1571
|
setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
|
|
902
1572
|
}
|
|
903
1573
|
|
|
904
|
-
//destroy
|
|
905
|
-
await md.destroy(setting)
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
})
|
|
1574
|
+
//destroy, n與nDeleted皆為實際刪除筆數, 條件無命中時為0且屬正常結果
|
|
1575
|
+
let r = await md.destroy(setting)
|
|
1576
|
+
let nDel = isnum(r) ? r : 0
|
|
1577
|
+
|
|
1578
|
+
//res
|
|
1579
|
+
res = {
|
|
1580
|
+
n: nDel,
|
|
1581
|
+
nDeleted: nDel,
|
|
1582
|
+
ok: 1,
|
|
1583
|
+
}
|
|
915
1584
|
|
|
916
1585
|
}
|
|
917
|
-
|
|
918
|
-
|
|
1586
|
+
catch (err) {
|
|
1587
|
+
isErr = true
|
|
1588
|
+
res = err
|
|
919
1589
|
}
|
|
1590
|
+
finally {
|
|
1591
|
+
|
|
1592
|
+
//closeSequelize
|
|
1593
|
+
if (instance === null) { //內部自動初始化得close
|
|
1594
|
+
await closeSequelize('delAll')
|
|
1595
|
+
}
|
|
920
1596
|
|
|
921
|
-
//closeSequelize
|
|
922
|
-
if (instance === null) { //內部自動初始化得close
|
|
923
|
-
await closeSequelize('delAll')
|
|
924
1597
|
}
|
|
925
1598
|
|
|
926
|
-
|
|
1599
|
+
//check
|
|
1600
|
+
if (isErr) {
|
|
1601
|
+
emitError('delAll', null, res)
|
|
1602
|
+
return Promise.reject(res)
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
//emit, 須於結果定案後發出
|
|
1606
|
+
emitChange('delAll', null, res)
|
|
1607
|
+
|
|
1608
|
+
return res
|
|
927
1609
|
}
|
|
928
1610
|
|
|
929
1611
|
|
|
@@ -1045,9 +1727,15 @@ function WOrmReladb(opt = {}) {
|
|
|
1045
1727
|
ee.select = function() {
|
|
1046
1728
|
return pmq(select, ...arguments)
|
|
1047
1729
|
}
|
|
1730
|
+
ee.selectByPk = function() {
|
|
1731
|
+
return pmq(selectByPk, ...arguments)
|
|
1732
|
+
}
|
|
1048
1733
|
ee.insert = function() {
|
|
1049
1734
|
return pmq(insert, ...arguments)
|
|
1050
1735
|
}
|
|
1736
|
+
ee.insertBulk = function() {
|
|
1737
|
+
return pmq(insertBulk, ...arguments)
|
|
1738
|
+
}
|
|
1051
1739
|
ee.save = function() {
|
|
1052
1740
|
return pmq(save, ...arguments)
|
|
1053
1741
|
}
|
|
@@ -1060,7 +1748,9 @@ function WOrmReladb(opt = {}) {
|
|
|
1060
1748
|
}
|
|
1061
1749
|
else {
|
|
1062
1750
|
ee.select = select
|
|
1751
|
+
ee.selectByPk = selectByPk
|
|
1063
1752
|
ee.insert = insert
|
|
1753
|
+
ee.insertBulk = insertBulk
|
|
1064
1754
|
ee.save = save
|
|
1065
1755
|
ee.del = del
|
|
1066
1756
|
ee.delAll = delAll
|