w-orm-reladb 1.0.47 → 1.0.48

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.
@@ -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 genID from 'wsemi/src/genID.mjs'
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.autoGenPK=true] 輸入若數據pk(id)欄位沒給時則自動給予隨機uuid,型別為布林值,預設true
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.autoGenPK)) {
78
- opt.autoGenPK = true
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
- let ee = new events.EventEmitter()
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
- console.log('no dialect in opt.url')
106
- return ee
120
+ throwUrlErr('no dialect in opt.url')
107
121
  }
108
122
  if (!arrHas(dialect, ['mssql', 'sqlite', 'mysql', 'mariadb', 'postgres'])) {
109
- console.log('invalid dialect in opt.url')
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
- console.log('invalid username or password in opt.url')
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
- console.log('invalid username in opt.url')
131
- return ee
142
+ throwUrlErr('invalid username in opt.url')
132
143
  }
133
144
  if (password === '') {
134
- console.log('invalid password in opt.url')
135
- return ee
145
+ throwUrlErr('invalid password in opt.url')
136
146
  }
137
147
  }
138
148
 
@@ -141,24 +151,161 @@ 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
- console.log('invalid host in opt.url')
153
- return ee
158
+ throwUrlErr('invalid host in opt.url')
154
159
  }
155
160
  if (!port) {
156
- console.log('invalid port in opt.url')
157
- return ee
161
+ throwUrlErr('invalid port in opt.url')
158
162
  }
159
163
  }
160
164
 
161
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))
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
+ * autoGenPk為true時未帶有效主鍵者自動產生,為false時往外拋
266
+ * 註: 未帶有效主鍵屬呼叫端未履行契約而非某一筆資料本身之問題,故為整批性錯誤而不降級為該筆ok為0,
267
+ * 若降級為逐筆結果,呼叫端易於整批resolve之下漏看,使[忘了給主鍵]靜默變成[少寫了幾筆]
268
+ * 註: 本函數須於任何寫入之前一次對全部數據完成,令拋錯時同批之有效筆數亦不會被寫入
269
+ * 註: 自動產生之主鍵值為字串,故補值前檢查主鍵欄位確為字串類型,不符者以明確訊息拋出,
270
+ * 而非任由資料庫回報型別錯誤; 本套件之model定義於手上得知主鍵型別,故不適用T6之[套件無從得知主鍵型別]例外
271
+ *
272
+ * @ignore
273
+ * @param {Object} md 輸入資料表model物件
274
+ * @param {Object} v 輸入數據物件
275
+ * @param {Number} k 輸入數據於陣列內之索引
276
+ * @returns {Object} 回傳補齊主鍵之數據物件
277
+ */
278
+ function procPk(md, v, k) {
279
+
280
+ //check
281
+ if (isEffPk(get(v, opt.pk))) {
282
+ return v
283
+ }
284
+
285
+ //check, autoGenPk為false時主鍵須由呼叫端自備
286
+ if (!opt.autoGenPk) {
287
+ throw new Error(`invalid data[${k}].${opt.pk}, autoGenPk is false`)
288
+ }
289
+
290
+ //at
291
+ let at = get(md, `rawAttributes.${opt.pk}`)
292
+ if (!iseobj(at)) {
293
+ throw new Error(`can not find pk column [${opt.pk}] in model [${opt.cl}]`)
294
+ }
295
+
296
+ //check, 自動產生之主鍵值為字串, 主鍵欄位須為字串類型方能容納
297
+ let tp = get(at, 'type.key', '')
298
+ if (!arrHas(tp, ['STRING', 'TEXT', 'CHAR', 'UUID', 'CITEXT'])) {
299
+ 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`)
300
+ }
301
+
302
+ //genIDSeq, 為UUIDv7格式而具單調遞增性, 主鍵接近順序遞增可減少索引頁面分裂
303
+ v[opt.pk] = genIDSeq()
304
+
305
+ return v
306
+ }
307
+
308
+
162
309
  /**
163
310
  * 初始化sequelize
164
311
  *
@@ -351,23 +498,27 @@ function WOrmReladb(opt = {}) {
351
498
  }
352
499
  }
353
500
 
501
+ //isErr, res
502
+ let isErr = false
503
+ let res = null
504
+
354
505
  //si
355
506
  let si = instance
356
507
  if (instance === null) {
357
508
  si = await initSequelize()
358
509
  }
359
- // else {
360
- // console.log('select use instance', instance)
361
- // }
362
510
 
363
- //rs
364
- let rs = null
365
- if (!si.err) {
511
+ try {
512
+
513
+ //check, 連線或匯入models失敗影響本次操作之全部數據, 屬整批性錯誤
514
+ if (si.err) {
515
+ throw si.err
516
+ }
366
517
 
367
518
  //md
368
519
  let md = si.mds[opt.cl]
369
520
 
370
- //setting
521
+ //setting, raw令回傳純數據物件而不含資料庫內部欄位
371
522
  let setting = {
372
523
  where: useFind,
373
524
  raw: true,
@@ -376,32 +527,150 @@ function WOrmReladb(opt = {}) {
376
527
  setting.transaction = transaction
377
528
  }
378
529
 
379
- //findAll
380
- rs = await md.findAll(setting)
530
+ //findAll, 恆回傳陣列, 無符合數據為空陣列
531
+ res = await md.findAll(setting)
532
+ if (!isarr(res)) {
533
+ res = []
534
+ }
381
535
 
382
536
  }
383
- else {
384
- ee.emit('error', si.err)
537
+ catch (err) {
538
+ isErr = true
539
+ res = err
385
540
  }
541
+ finally {
386
542
 
387
- //closeSequelize
388
- if (instance === null) { //內部自動初始化得close
389
- await closeSequelize('select')
543
+ //closeSequelize
544
+ if (instance === null) { //內部自動初始化得close
545
+ await closeSequelize('select')
546
+ }
547
+
548
+ }
549
+
550
+ //check
551
+ if (isErr) {
552
+ emitError('select', null, res)
553
+ return Promise.reject(res)
390
554
  }
391
555
 
392
- return rs
556
+ return res
393
557
  }
394
558
 
395
559
 
396
560
  /**
397
- * 插入數據,插入同樣數據會自動產生不同_id,故insert前需自行判斷有無重複
561
+ * 由主鍵查詢單筆數據,因由資料庫依主鍵索引取值且僅回傳單筆,不需如select提取全部符合數據再處理,故數據量大時效能較佳
562
+ *
563
+ * 註: 主鍵欄位取自opt.pk,預設為id,支援由呼叫端指定
564
+ * 註: 主鍵未命中或主鍵值無效皆回傳null而不reject,[命中]之判定基準與insert、save、del內對既有數據之認定一致
565
+ * 註: 本函數不得有副作用,故不創建資料表
566
+ *
567
+ * @memberOf WOrmReladb
568
+ * @param {String|Number} pk 輸入主鍵值,即數據內opt.pk欄位之值
569
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
570
+ * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
571
+ * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
572
+ * @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵或主鍵值無效則回傳null,reject回傳錯誤訊息
573
+ */
574
+ async function selectByPk(pk, option = {}) {
575
+
576
+ //check, 未給有效主鍵值視為查無數據, 不送查詢以免無效值被轉為null而誤中其他數據
577
+ if (!isEffPk(pk)) {
578
+ return null
579
+ }
580
+
581
+ //instance
582
+ let instance = get(option, 'instance', null)
583
+
584
+ //transaction
585
+ let transaction = get(option, 'transaction', null)
586
+
587
+ //check
588
+ if (opt.useEncryption && dialect === 'sqlite') {
589
+ if (transaction !== null) {
590
+ console.log('@journeyapps/sqlcipher can not support transaction.')
591
+ }
592
+ }
593
+
594
+ //isErr, res
595
+ let isErr = false
596
+ let res = null
597
+
598
+ //si
599
+ let si = instance
600
+ if (instance === null) {
601
+ si = await initSequelize()
602
+ }
603
+
604
+ try {
605
+
606
+ //check
607
+ if (si.err) {
608
+ throw si.err
609
+ }
610
+
611
+ //md
612
+ let md = si.mds[opt.cl]
613
+
614
+ //setting
615
+ let setting = {
616
+ where: { [opt.pk]: pk },
617
+ raw: true,
618
+ }
619
+ if (transaction !== null) {
620
+ setting.transaction = transaction
621
+ }
622
+
623
+ //findOne
624
+ let v = await md.findOne(setting)
625
+
626
+ //check, 判定基準與insert、save、del內對既有數據之認定一致
627
+ if (iseobj(v)) {
628
+ res = v
629
+ }
630
+ else {
631
+ //不存在主鍵, 回傳null
632
+ res = null
633
+ }
634
+
635
+ }
636
+ catch (err) {
637
+ isErr = true
638
+ res = err
639
+ }
640
+ finally {
641
+
642
+ //closeSequelize
643
+ if (instance === null) { //內部自動初始化得close
644
+ await closeSequelize('selectByPk')
645
+ }
646
+
647
+ }
648
+
649
+ //check
650
+ if (isErr) {
651
+ emitError('selectByPk', null, res)
652
+ return Promise.reject(res)
653
+ }
654
+
655
+ return res
656
+ }
657
+
658
+
659
+ /**
660
+ * 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
661
+ *
662
+ * 由資料庫於主鍵之唯一約束上原子完成[檢查主鍵不存在]與[寫入],併發時同一主鍵僅有一次成功
663
+ *
664
+ * 註: n為輸入筆數即本次嘗試插入之基準,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
665
+ * 註: 同批含重複主鍵時僅首筆計入nInserted,其餘視為已存在
666
+ * 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
398
667
  *
399
668
  * @memberOf WOrmReladb
400
669
  * @param {Object|Array} data 輸入數據物件或陣列
401
670
  * @param {Object} [option={}] 輸入設定物件,預設為{}
402
671
  * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
403
672
  * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
404
- * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
673
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果{n,nInserted,ok},reject回傳錯誤訊息
405
674
  */
406
675
  async function insert(data, option = {}) {
407
676
 
@@ -430,19 +699,22 @@ function WOrmReladb(opt = {}) {
430
699
  //cloneDeep
431
700
  data = cloneDeep(data)
432
701
 
433
- //pm
434
- let pm = genPm()
702
+ //isErr, res
703
+ let isErr = false
704
+ let res = null
435
705
 
436
706
  //si
437
707
  let si = instance
438
708
  if (instance === null) {
439
709
  si = await initSequelize()
440
710
  }
441
- // else {
442
- // console.log('insert use instance', instance)
443
- // }
444
711
 
445
- if (!si.err) {
712
+ try {
713
+
714
+ //check
715
+ if (si.err) {
716
+ throw si.err
717
+ }
446
718
 
447
719
  //md
448
720
  let md = si.mds[opt.cl]
@@ -452,18 +724,13 @@ function WOrmReladb(opt = {}) {
452
724
  data = [data]
453
725
  }
454
726
 
455
- //n
456
- let n = size(data)
727
+ //procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
728
+ data = map(data, (v, k) => {
729
+ return procPk(md, v, k)
730
+ })
457
731
 
458
- //check
459
- if (opt.autoGenPK) {
460
- data = map(data, function(v) {
461
- if (!v[opt.pk]) {
462
- v[opt.pk] = genID()
463
- }
464
- return v
465
- })
466
- }
732
+ //nAll, n之基準為輸入筆數
733
+ let nAll = size(data)
467
734
 
468
735
  //setting
469
736
  let setting = { }
@@ -471,53 +738,100 @@ function WOrmReladb(opt = {}) {
471
738
  setting.transaction = transaction
472
739
  }
473
740
 
474
- //bulkCreate
475
- await md.bulkCreate(data, setting)
476
- .then((res) => {
477
- res = { n, nInserted: n, ok: 1 }
478
- pm.resolve(res)
479
- ee.emit('change', 'insert', data, res)
480
- })
481
- .catch(({ original }) => {
482
- ee.emit('error', original)
483
- pm.reject({ n: 0, ok: 0 })
484
- })
741
+ //create, 逐筆插入以取得精確之nInserted
742
+ //註: 不採bulkCreate配合ignoreDuplicates, 因mssql未宣告該能力而選項會被靜默忽略仍撞唯一約束,
743
+ //且bulkCreate無從回報實際插入筆數; 單筆INSERT本即由主鍵之唯一約束原子完成檢查與寫入
744
+ let nInserted = 0
745
+ await pmSeries(data, async(v) => {
746
+
747
+ //bIns, 撞既有主鍵者視為已存在而跳過, 其餘錯誤影響全部數據須往外拋
748
+ let bIns = await md.create(v, setting)
749
+ .then(() => {
750
+ return true
751
+ })
752
+ .catch((err) => {
753
+ if (isDupPkError(err)) {
754
+ return false
755
+ }
756
+ throw err
757
+ })
758
+
759
+ //nInserted
760
+ if (bIns) {
761
+ nInserted += 1
762
+ }
763
+
764
+ return bIns
765
+ })
766
+
767
+ //res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
768
+ res = {
769
+ n: nAll,
770
+ nInserted,
771
+ ok: 1,
772
+ }
485
773
 
486
774
  }
487
- else {
488
- pm.reject(si.err)
775
+ catch (err) {
776
+ isErr = true
777
+ res = err
489
778
  }
779
+ finally {
780
+
781
+ //closeSequelize
782
+ if (instance === null) { //內部自動初始化得close
783
+ await closeSequelize('insert')
784
+ }
490
785
 
491
- //closeSequelize
492
- if (instance === null) { //內部自動初始化得close
493
- await closeSequelize('insert')
494
786
  }
495
787
 
496
- return pm
788
+ //check
789
+ if (isErr) {
790
+ emitError('insert', data, res)
791
+ return Promise.reject(res)
792
+ }
793
+
794
+ //emit, 須於結果定案後發出
795
+ emitChange('insert', data, res)
796
+
797
+ return res
497
798
  }
498
799
 
499
800
 
500
801
  /**
501
- * 儲存數據
802
+ * 批次插入數據,全批視為一個單位:全部插入成功,或一筆都不寫入
803
+ *
804
+ * 本函數非insert之加速版,兩者衝突政策不同:insert於主鍵已存在時跳過該筆且整批ok為1,
805
+ * 本函數則整批reject且不寫入任何一筆,同批含重複主鍵者亦視為衝突。
806
+ * 確無衝突時兩者之可觀察結果完全相同,差異僅於有衝突時顯現。
807
+ *
808
+ * 註: n為輸入筆數即本次嘗試插入之基準;nInserted於成功時恆等於n,雖無額外資訊仍保留,
809
+ * 令呼叫端得與insert共用同一段結果處理程式碼
810
+ * 註: 不提供逐筆結果,故不出現ok為0與err;需要逐筆處置者改用insert
811
+ * 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
812
+ * 註: [全有全無]為獨立於T7之額外要求——T7僅保證每筆之[檢查主鍵不存在]與[寫入]為原子,不保證整批。
813
+ * mssql因綁定參數數量上限會由驅動層自動拆為多語句送出,實測1000筆之批次於末筆撞主鍵時已有946筆落盤,
814
+ * 故一律以交易包覆並於失敗時回滾;呼叫端已給transaction時改以巢狀交易(SAVEPOINT)包覆,
815
+ * 令回滾範圍限於本次呼叫而不影響呼叫端交易內先前之寫入
502
816
  *
503
817
  * @memberOf WOrmReladb
504
818
  * @param {Object|Array} data 輸入數據物件或陣列
505
819
  * @param {Object} [option={}] 輸入設定物件,預設為{}
506
820
  * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
507
821
  * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
508
- * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
509
- * @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
822
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果{n,nInserted,ok},reject回傳錯誤訊息
510
823
  */
511
- async function save(data, option = {}) {
824
+ async function insertBulk(data, option = {}) {
512
825
 
513
826
  //check
514
827
  if (!iseobj(data) && !isearr(data)) {
515
- return []
828
+ return {
829
+ n: 0,
830
+ nInserted: 0,
831
+ ok: 1,
832
+ }
516
833
  }
517
834
 
518
- //cloneDeep
519
- data = cloneDeep(data)
520
-
521
835
  //instance
522
836
  let instance = get(option, 'instance', null)
523
837
 
@@ -531,22 +845,25 @@ function WOrmReladb(opt = {}) {
531
845
  }
532
846
  }
533
847
 
534
- //autoInsert
535
- let autoInsert = get(option, 'autoInsert', true)
848
+ //cloneDeep
849
+ data = cloneDeep(data)
536
850
 
537
- //pm
538
- let pm = genPm()
851
+ //isErr, res
852
+ let isErr = false
853
+ let res = null
539
854
 
540
855
  //si
541
856
  let si = instance
542
857
  if (instance === null) {
543
858
  si = await initSequelize()
544
859
  }
545
- // else {
546
- // console.log('save use instance', instance)
547
- // }
548
860
 
549
- if (!si.err) {
861
+ try {
862
+
863
+ //check
864
+ if (si.err) {
865
+ throw si.err
866
+ }
550
867
 
551
868
  //md
552
869
  let md = si.mds[opt.cl]
@@ -556,157 +873,379 @@ function WOrmReladb(opt = {}) {
556
873
  data = [data]
557
874
  }
558
875
 
559
- //check
560
- if (opt.autoGenPK) {
561
- data = map(data, function(v) {
562
- if (!v[opt.pk]) {
563
- v[opt.pk] = genID()
564
- }
565
- return v
566
- })
567
- }
876
+ //procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批皆不會被寫入
877
+ data = map(data, (v, k) => {
878
+ return procPk(md, v, k)
879
+ })
568
880
 
569
- // //tr
570
- // let t = null
571
- // let tr = {}
572
- // if (atomic) {
573
- // t = await sequelize.transaction() //使用Unmanaged transaction (then-callback)
574
- // tr = {
575
- // transaction: t
576
- // }
577
- // }
881
+ //nAll, n之基準為輸入筆數
882
+ let nAll = size(data)
578
883
 
579
- //pmSeries
580
- await pmSeries(data, async function(v) {
581
- let pmm = genPm()
884
+ //useTx, sqlcipher不支援transaction, 該情形下改倚賴sqlite單一語句之原子性
885
+ let useTx = !(opt.useEncryption && dialect === 'sqlite')
582
886
 
583
- //err
584
- let err = null
887
+ //t, 呼叫端未給交易時自開; 已給時以巢狀交易(SAVEPOINT)包覆令回滾範圍限於本次呼叫
888
+ let t = null
889
+ if (useTx) {
890
+ t = (transaction !== null)
891
+ ? await md.sequelize.transaction({ transaction })
892
+ : await md.sequelize.transaction()
893
+ }
585
894
 
586
- //r
587
- let r
588
- if (v[opt.pk]) {
589
- //有id
895
+ //setting
896
+ let setting = {}
897
+ if (t !== null) {
898
+ setting.transaction = t
899
+ }
900
+ else if (transaction !== null) {
901
+ setting.transaction = transaction
902
+ }
590
903
 
591
- //setting
592
- let setting = {
593
- where: { [opt.pk]: v[opt.pk] },
594
- raw: true,
595
- }
596
- if (transaction !== null) {
597
- setting.transaction = transaction
598
- }
904
+ try {
599
905
 
600
- //findOne
601
- r = await md.findOne(setting)
602
- .catch((error) => {
603
- ee.emit('error', error)
604
- err = error
605
- })
906
+ //bulkCreate, 未加任何跳過選項, 故任一筆撞主鍵即整句失敗, 同批重複主鍵亦於此被偵測為衝突
907
+ await md.bulkCreate(data, setting)
606
908
 
909
+ if (t !== null) {
910
+ await t.commit()
607
911
  }
608
- else {
609
- //沒有id
610
- err = `${opt.pk} is invalid`
912
+
913
+ }
914
+ catch (err) {
915
+ if (t !== null) {
916
+ await t.rollback().catch(() => {})
611
917
  }
918
+ throw err
919
+ }
612
920
 
613
- if (r) {
614
- //有找到資料
921
+ //res, 全有全無故成功時nInserted恆等於n
922
+ res = {
923
+ n: nAll,
924
+ nInserted: nAll,
925
+ ok: 1,
926
+ }
615
927
 
616
- //setting
617
- let setting = {
618
- where: { [opt.pk]: v[opt.pk] },
619
- }
620
- if (transaction !== null) {
621
- setting.transaction = transaction
622
- }
928
+ }
929
+ catch (err) {
930
+ isErr = true
931
+ res = err
932
+ }
933
+ finally {
623
934
 
624
- let rr = await md.update(v, setting)
625
- .catch((error) => {
626
- ee.emit('error', error)
627
- err = error
628
- })
935
+ //closeSequelize
936
+ if (instance === null) { //內部自動初始化得close
937
+ await closeSequelize('insertBulk')
938
+ }
629
939
 
630
- if (rr) {
631
- //console.log('update 有更新資料', rr)
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
- }
940
+ }
638
941
 
639
- }
640
- else {
641
- //沒有找到資料
942
+ //check
943
+ if (isErr) {
944
+ emitError('insertBulk', data, res)
945
+ return Promise.reject(res)
946
+ }
642
947
 
643
- //autoInsert
644
- if (autoInsert) {
948
+ //emit, 須於結果定案後發出
949
+ emitChange('insertBulk', data, res)
645
950
 
646
- //setting
647
- let setting = { }
648
- if (transaction !== null) {
649
- setting.transaction = transaction
650
- }
951
+ return res
952
+ }
953
+
954
+
955
+ /**
956
+ * 儲存單筆數據
957
+ *
958
+ * 採條件寫入配合衝突偵測與重試: 每次寫入本身為單一條件式原子語句——插入由主鍵之唯一約束判定、
959
+ * 更新由WHERE主鍵之比對判定,皆非由預讀值決定其成敗;預讀僅用於選擇發出哪一條語句,
960
+ * 選錯不會產生錯誤結果,只會被偵測並重試(插入撞既有主鍵、更新未命中任何列),故縱使預讀值已過期,
961
+ * 最終結果仍與該次操作單獨執行時相同。
962
+ * 註: 不採sequelize之upsert,因sqlite無從於單一語句內回報本次係插入或更新(其created恆為null),
963
+ * 而insert與save之規格要求nInserted精確,兩者於sqlite互斥,故改採本形式
964
+ * 註: [內容相同]之判定基準為將待寫入物件合併進現值後與現值相同,相同則不寫入而nModified為0,
965
+ * 合併取淺層以與後端整欄取代之寫入行為一致,令nModified忠實反映資料庫端是否真的寫入
966
+ *
967
+ * @ignore
968
+ * @param {Object} md 輸入資料表model物件
969
+ * @param {Object} v 輸入數據物件
970
+ * @param {Boolean} autoInsert 輸入是否於查無數據時自動改以插入處理布林值
971
+ * @param {Object|null} transaction 輸入交易(transaction)物件
972
+ * @param {Array} ks 輸入model之欄位名陣列
973
+ * @returns {Promise} 回傳Promise,resolve回傳本筆儲存結果,本筆失敗時亦resolve並以ok為0回報
974
+ */
975
+ async function saveOne(md, v, autoInsert, transaction, ks) {
976
+
977
+ //nTry, 預測錯誤時重試, 給予有限次數令其收斂
978
+ let nTry = 3
979
+
980
+ //rest
981
+ let rest = null
982
+
983
+ //inserted, 供結果定案後發出insert事件用
984
+ let inserted = false
651
985
 
652
- //create
653
- let rr = await md.create(v, setting)
654
- .catch((error) => {
655
- ee.emit('error', error)
656
- err = error
657
- })
986
+ //pkv
987
+ let pkv = get(v, opt.pk)
658
988
 
659
- if (rr) {
660
- //console.log('create 有插入資料', rr)
661
- pmm.resolve({ n: 1, nInserted: 1, ok: 1 })
989
+ //setting
990
+ let settingRead = {
991
+ where: { [opt.pk]: pkv },
992
+ raw: true,
993
+ }
994
+ let settingUpd = {
995
+ where: { [opt.pk]: pkv },
996
+ }
997
+ let settingIns = {}
998
+ if (transaction !== null) {
999
+ settingRead.transaction = transaction
1000
+ settingUpd.transaction = transaction
1001
+ settingIns.transaction = transaction
1002
+ }
1003
+
1004
+ for (let i = 0; i < nTry; i++) {
1005
+
1006
+ try {
1007
+
1008
+ //預讀, 供判斷內容是否相同之快速路徑, 其結果不用於決定寫入內容
1009
+ let _v = await md.findOne(settingRead)
1010
+
1011
+ if (iseobj(_v)) {
1012
+ //主鍵存在
1013
+
1014
+ //_vt, vt, 以待寫入物件之非主鍵欄位淺層覆蓋現值後與現值比對
1015
+ let _vt = omit(_v, opt.pk)
1016
+ let vt = {
1017
+ ..._vt,
1018
+ ...pick(omit(v, opt.pk), ks),
1019
+ }
1020
+
1021
+ //check, 內容相同者不寫入
1022
+ if (isEqual(vt, _vt)) {
1023
+ rest = {
1024
+ n: 1,
1025
+ nInserted: 0,
1026
+ nModified: 0,
1027
+ ok: 1,
662
1028
  }
663
- else {
664
- //console.log('create 沒有插入資料', err)
665
- pmm.resolve({ n: 1, nInserted: 0, ok: 0 })
1029
+ break
1030
+ }
1031
+
1032
+ //update, 單一UPDATE語句由WHERE主鍵原子完成查找與更新
1033
+ let rr = await md.update(v, settingUpd)
1034
+ let nAff = get(rr, 0, 0)
1035
+
1036
+ if (nAff > 0) {
1037
+ rest = {
1038
+ n: 1,
1039
+ nInserted: 0,
1040
+ nModified: 1,
1041
+ ok: 1,
666
1042
  }
1043
+ break
1044
+ }
1045
+
1046
+ //該列已被他方刪除, 重試改走插入
1047
+ continue
1048
+ }
667
1049
 
1050
+ //主鍵不存在且未開啟autoInsert則不寫入以免無中生有
1051
+ if (!autoInsert) {
1052
+ rest = {
1053
+ n: 0,
1054
+ nInserted: 0,
1055
+ nModified: 0,
1056
+ ok: 1,
668
1057
  }
669
- else {
670
- //console.log('findOne 沒有找到資料也不自動插入', err)
671
- pmm.resolve({ n: 0, nModified: 0, ok: 1 })
1058
+ break
1059
+ }
1060
+
1061
+ //create, 單一INSERT語句由主鍵之唯一約束原子完成檢查與寫入
1062
+ let bIns = await md.create(v, settingIns)
1063
+ .then(() => {
1064
+ return true
1065
+ })
1066
+ .catch((err) => {
1067
+ if (isDupPkError(err)) {
1068
+ return false
1069
+ }
1070
+ throw err
1071
+ })
1072
+
1073
+ if (bIns) {
1074
+ rest = {
1075
+ n: 1,
1076
+ nInserted: 1,
1077
+ nModified: 0,
1078
+ ok: 1,
672
1079
  }
1080
+ inserted = true
1081
+ break
1082
+ }
673
1083
 
1084
+ //他方已插入同一主鍵, 重試改走更新
1085
+ continue
1086
+ }
1087
+ catch (err) {
1088
+
1089
+ //其餘錯誤視為本筆失敗, 不中斷整批, 由呼叫端以ok與err判讀
1090
+ rest = {
1091
+ n: 1,
1092
+ nInserted: 0,
1093
+ nModified: 0,
1094
+ ok: 0,
1095
+ err: getErrMsg(err),
674
1096
  }
1097
+ break
1098
+ }
1099
+
1100
+ }
1101
+
1102
+ //check, 重試耗盡仍未定案
1103
+ if (rest === null) {
1104
+ rest = {
1105
+ n: 1,
1106
+ nInserted: 0,
1107
+ nModified: 0,
1108
+ ok: 0,
1109
+ err: `can not save data by ${opt.pk}[${pkv}] in ${nTry} tries`,
1110
+ }
1111
+ }
1112
+
1113
+ //emit, 須於本筆結果定案後發出, 避免訂閱函數拋錯影響本筆結果
1114
+ if (inserted) {
1115
+ emitChange('insert', [v], rest)
1116
+ }
1117
+ if (rest.ok === 0) {
1118
+ emitError('save', [v], rest.err)
1119
+ }
1120
+
1121
+ return rest
1122
+ }
1123
+
1124
+
1125
+ /**
1126
+ * 儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且option.autoInsert為true(預設)時改為插入
1127
+ *
1128
+ * 註: 回傳陣列恆與輸入等長,輸入單一物件亦回傳長度1之陣列
1129
+ * 註: n為主鍵命中筆數,值為0或1,命中(不論內容有無變更)或經插入而產生皆為1;nInserted與nModified恆同時出現
1130
+ * 註: [內容相同]之判定基準為將待寫入物件合併進現值後與現值相同,非待寫入物件與現值全等,
1131
+ * 故只給部份欄位且該些欄位值皆與現值相同時,合併結果等於現值,nModified為0
1132
+ * 註: 本筆失敗不中斷整批,該筆以ok為0並附err回報
1133
+ * 註: opt.autoGenPk為true(預設)時未帶有效主鍵者自動產生,為false時未帶有效主鍵即reject且同批皆不寫入
1134
+ *
1135
+ * @memberOf WOrmReladb
1136
+ * @param {Object|Array} data 輸入數據物件或陣列
1137
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
1138
+ * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
1139
+ * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
1140
+ * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
1141
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列[{n,nInserted,nModified,ok}],reject回傳錯誤訊息
1142
+ */
1143
+ async function save(data, option = {}) {
1144
+
1145
+ //check
1146
+ if (!iseobj(data) && !isearr(data)) {
1147
+ return []
1148
+ }
1149
+
1150
+ //cloneDeep
1151
+ data = cloneDeep(data)
1152
+
1153
+ //instance
1154
+ let instance = get(option, 'instance', null)
1155
+
1156
+ //transaction
1157
+ let transaction = get(option, 'transaction', null)
1158
+
1159
+ //check
1160
+ if (opt.useEncryption && dialect === 'sqlite') {
1161
+ if (transaction !== null) {
1162
+ console.log('@journeyapps/sqlcipher can not support transaction.')
1163
+ }
1164
+ }
1165
+
1166
+ //autoInsert
1167
+ let autoInsert = get(option, 'autoInsert', true)
1168
+
1169
+ //isErr, res
1170
+ let isErr = false
1171
+ let res = null
1172
+
1173
+ //si
1174
+ let si = instance
1175
+ if (instance === null) {
1176
+ si = await initSequelize()
1177
+ }
1178
+
1179
+ try {
1180
+
1181
+ //check
1182
+ if (si.err) {
1183
+ throw si.err
1184
+ }
1185
+
1186
+ //md
1187
+ let md = si.mds[opt.cl]
1188
+
1189
+ //check
1190
+ if (!isarr(data)) {
1191
+ data = [data]
1192
+ }
675
1193
 
676
- pmm._err = err //避免eslint錯誤訊息
677
- return pmm
1194
+ //procPk, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
1195
+ data = map(data, (v, k) => {
1196
+ return procPk(md, v, k)
1197
+ })
1198
+
1199
+ //ks, model之欄位名, 供合併後比對時濾除非欄位之鍵
1200
+ let ks = keys(get(md, 'rawAttributes', {}))
1201
+
1202
+ //pmSeries
1203
+ res = await pmSeries(data, async(v) => {
1204
+ return saveOne(md, v, autoInsert, transaction, ks)
678
1205
  })
679
- .then((res) => {
680
- pm.resolve(res)
681
- ee.emit('change', 'save', data, res)
682
- })
683
- .catch((error) => {
684
- pm.reject(error)
685
- })
686
1206
 
687
1207
  }
688
- else {
689
- pm.reject(si.err)
1208
+ catch (err) {
1209
+ isErr = true
1210
+ res = err
690
1211
  }
1212
+ finally {
1213
+
1214
+ //closeSequelize
1215
+ if (instance === null) { //內部自動初始化得close
1216
+ await closeSequelize('save')
1217
+ }
691
1218
 
692
- //closeSequelize
693
- if (instance === null) { //內部自動初始化得close
694
- await closeSequelize('save')
695
1219
  }
696
1220
 
697
- return pm
1221
+ //check
1222
+ if (isErr) {
1223
+ emitError('save', data, res)
1224
+ return Promise.reject(res)
1225
+ }
1226
+
1227
+ //emit, 須於結果定案後發出
1228
+ emitChange('save', data, res)
1229
+
1230
+ return res
698
1231
  }
699
1232
 
700
1233
 
701
1234
  /**
702
1235
  * 刪除數據
703
1236
  *
1237
+ * 註: 回傳陣列恆與輸入等長,各筆之n與nDeleted皆為主鍵命中筆數,值為0或1,未命中為0且屬正常結果
1238
+ * 註: 判斷某筆是否真的被刪除一律以nDeleted為準
1239
+ * 註: 未帶有效主鍵者為該筆之輸入問題,回ok為0與err且不送查詢條件(以免無效值被轉為null而誤中其他數據),
1240
+ * 不中斷其餘筆數,以此與[主鍵未命中]之ok為1區辨
1241
+ * 註: 本函數不受opt.autoGenPk影響,於任一設定下皆不補值
1242
+ *
704
1243
  * @memberOf WOrmReladb
705
1244
  * @param {Object|Array} data 輸入數據物件或陣列,會查找各數據的opt.pk值,有存在者就刪除
706
1245
  * @param {Object} [option={}] 輸入設定物件,預設為{}
707
1246
  * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
708
1247
  * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
709
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
1248
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列[{n,nDeleted,ok}],reject回傳錯誤訊息
710
1249
  */
711
1250
  async function del(data, option = {}) {
712
1251
 
@@ -731,19 +1270,22 @@ function WOrmReladb(opt = {}) {
731
1270
  }
732
1271
  }
733
1272
 
734
- //pm
735
- let pm = genPm()
1273
+ //isErr, res
1274
+ let isErr = false
1275
+ let res = null
736
1276
 
737
1277
  //si
738
1278
  let si = instance
739
1279
  if (instance === null) {
740
1280
  si = await initSequelize()
741
1281
  }
742
- // else {
743
- // console.log('del use instance', instance)
744
- // }
745
1282
 
746
- if (!si.err) {
1283
+ try {
1284
+
1285
+ //check
1286
+ if (si.err) {
1287
+ throw si.err
1288
+ }
747
1289
 
748
1290
  //md
749
1291
  let md = si.mds[opt.cl]
@@ -754,106 +1296,111 @@ function WOrmReladb(opt = {}) {
754
1296
  }
755
1297
 
756
1298
  //pmSeries
757
- await pmSeries(data, async function(v) {
758
- let pmm = genPm()
1299
+ res = await pmSeries(data, async(v) => {
759
1300
 
760
- //err
761
- let err = null
1301
+ //rest
1302
+ let rest = null
762
1303
 
763
- //r
764
- let r
765
- if (v[opt.pk]) {
766
- //有id
1304
+ //pkv
1305
+ let pkv = get(v, opt.pk)
767
1306
 
768
- //setting
769
- let setting = {
770
- where: { [opt.pk]: v[opt.pk] },
771
- raw: true,
772
- }
773
- if (transaction !== null) {
774
- setting.transaction = transaction
1307
+ //check, 未帶有效主鍵者不補值亦不送查詢, 直接視為該筆無法處理
1308
+ if (!isEffPk(pkv)) {
1309
+
1310
+ //rest
1311
+ rest = {
1312
+ n: 0,
1313
+ nDeleted: 0,
1314
+ ok: 0,
1315
+ err: `invalid ${opt.pk}[${pkv}]`,
775
1316
  }
776
1317
 
777
- //findOne
778
- r = await md.findOne(setting)
779
- .catch((error) => {
780
- ee.emit('error', error)
781
- err = error
782
- })
1318
+ //emit
1319
+ emitError('del', [v], rest.err)
1320
+
1321
+ return rest
1322
+ }
783
1323
 
1324
+ //setting
1325
+ let setting = {
1326
+ where: { [opt.pk]: pkv },
784
1327
  }
785
- else {
786
- //沒有id
787
- err = `${opt.pk} is invalid`
1328
+ if (transaction !== null) {
1329
+ setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
788
1330
  }
789
1331
 
790
- if (r) {
791
- //有找到資料
1332
+ //destroy, 單一DELETE語句即可取得命中筆數, 不另行findOne以免多一次往返且與刪除間存在競態
1333
+ await md.destroy(setting)
1334
+ .then((r) => {
792
1335
 
793
- //setting
794
- let setting = {
795
- where: { [opt.pk]: v[opt.pk] },
796
- }
797
- if (transaction !== null) {
798
- setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
799
- }
1336
+ //nDel, n與nDeleted皆為主鍵命中筆數, 未命中為0且屬正常結果
1337
+ let nDel = isnum(r) ? r : 0
800
1338
 
801
- //destroy
802
- let rr = await md.destroy(setting)
803
- .catch((error) => {
804
- ee.emit('error', error)
805
- err = error
806
- })
1339
+ //rest
1340
+ rest = {
1341
+ n: nDel,
1342
+ nDeleted: nDel,
1343
+ ok: 1,
1344
+ }
807
1345
 
808
- if (rr) {
809
- //console.log('destroy 有刪除資料', rr)
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
- }
1346
+ })
1347
+ .catch((err) => {
816
1348
 
817
- }
818
- else {
819
- //console.log('findOne 沒有找到資料', err)
820
- pmm.resolve({ n: 0, nDeleted: 0, ok: 1 })
821
- }
1349
+ //rest, 本筆失敗不中斷整批
1350
+ rest = {
1351
+ n: 1,
1352
+ nDeleted: 0,
1353
+ ok: 0,
1354
+ err: getErrMsg(err),
1355
+ }
822
1356
 
823
- pmm._err = err //避免eslint錯誤訊息
824
- return pmm
1357
+ //emit
1358
+ emitError('del', [v], rest.err)
1359
+
1360
+ })
1361
+
1362
+ return rest
825
1363
  })
826
- .then((res) => {
827
- pm.resolve(res)
828
- ee.emit('change', 'del', data, res)
829
- })
830
- .catch((res) => {
831
- pm.reject(res)
832
- })
833
1364
 
834
1365
  }
835
- else {
836
- pm.reject(si.err)
1366
+ catch (err) {
1367
+ isErr = true
1368
+ res = err
837
1369
  }
1370
+ finally {
1371
+
1372
+ //closeSequelize
1373
+ if (instance === null) { //內部自動初始化得close
1374
+ await closeSequelize('del')
1375
+ }
838
1376
 
839
- //closeSequelize
840
- if (instance === null) { //內部自動初始化得close
841
- await closeSequelize('del')
842
1377
  }
843
1378
 
844
- return pm
1379
+ //check
1380
+ if (isErr) {
1381
+ emitError('del', data, res)
1382
+ return Promise.reject(res)
1383
+ }
1384
+
1385
+ //emit, 須於結果定案後發出
1386
+ emitChange('del', data, res)
1387
+
1388
+ return res
845
1389
  }
846
1390
 
847
1391
 
848
1392
  /**
849
- * 刪除數據,需與del分開,避免未傳數據導致直接刪除全表
1393
+ * 依條件刪除多筆數據,需與del分開,避免未傳數據導致直接刪除全表
1394
+ *
1395
+ * 註: n與nDeleted皆為實際刪除筆數,兩者恆相等,n不得取全表筆數
1396
+ * 註: find未給或為空物件時刪除全部數據;條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤
850
1397
  *
851
1398
  * @memberOf WOrmReladb
852
1399
  * @param {Object} [find={}] 輸入刪除條件物件,不給予find則代表刪除全部數據
853
1400
  * @param {Object} [option={}] 輸入設定物件,預設為{}
854
1401
  * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
855
1402
  * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
856
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
1403
+ * @returns {Promise} 回傳Promise,resolve回傳刪除結果{n,nDeleted,ok},reject回傳錯誤訊息
857
1404
  */
858
1405
  async function delAll(find = {}, option = {}) {
859
1406
 
@@ -876,19 +1423,22 @@ function WOrmReladb(opt = {}) {
876
1423
  }
877
1424
  }
878
1425
 
879
- //pm
880
- let pm = genPm()
1426
+ //isErr, res
1427
+ let isErr = false
1428
+ let res = null
881
1429
 
882
1430
  //si
883
1431
  let si = instance
884
1432
  if (instance === null) {
885
1433
  si = await initSequelize()
886
1434
  }
887
- // else {
888
- // console.log('delAll use instance', instance)
889
- // }
890
1435
 
891
- if (!si.err) {
1436
+ try {
1437
+
1438
+ //check
1439
+ if (si.err) {
1440
+ throw si.err
1441
+ }
892
1442
 
893
1443
  //md
894
1444
  let md = si.mds[opt.cl]
@@ -901,29 +1451,41 @@ function WOrmReladb(opt = {}) {
901
1451
  setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
902
1452
  }
903
1453
 
904
- //destroy
905
- await md.destroy(setting)
906
- .then((res) => {
907
- res = { n: res, nDeleted: res, ok: 1 }
908
- pm.resolve(res)
909
- ee.emit('change', 'delAll', null, res)
910
- })
911
- .catch((error) => {
912
- ee.emit('error', error)
913
- pm.reject({ n: 0, ok: 0 })
914
- })
1454
+ //destroy, n與nDeleted皆為實際刪除筆數, 條件無命中時為0且屬正常結果
1455
+ let r = await md.destroy(setting)
1456
+ let nDel = isnum(r) ? r : 0
1457
+
1458
+ //res
1459
+ res = {
1460
+ n: nDel,
1461
+ nDeleted: nDel,
1462
+ ok: 1,
1463
+ }
915
1464
 
916
1465
  }
917
- else {
918
- pm.reject(si.err)
1466
+ catch (err) {
1467
+ isErr = true
1468
+ res = err
919
1469
  }
1470
+ finally {
1471
+
1472
+ //closeSequelize
1473
+ if (instance === null) { //內部自動初始化得close
1474
+ await closeSequelize('delAll')
1475
+ }
920
1476
 
921
- //closeSequelize
922
- if (instance === null) { //內部自動初始化得close
923
- await closeSequelize('delAll')
924
1477
  }
925
1478
 
926
- return pm
1479
+ //check
1480
+ if (isErr) {
1481
+ emitError('delAll', null, res)
1482
+ return Promise.reject(res)
1483
+ }
1484
+
1485
+ //emit, 須於結果定案後發出
1486
+ emitChange('delAll', null, res)
1487
+
1488
+ return res
927
1489
  }
928
1490
 
929
1491
 
@@ -1045,9 +1607,15 @@ function WOrmReladb(opt = {}) {
1045
1607
  ee.select = function() {
1046
1608
  return pmq(select, ...arguments)
1047
1609
  }
1610
+ ee.selectByPk = function() {
1611
+ return pmq(selectByPk, ...arguments)
1612
+ }
1048
1613
  ee.insert = function() {
1049
1614
  return pmq(insert, ...arguments)
1050
1615
  }
1616
+ ee.insertBulk = function() {
1617
+ return pmq(insertBulk, ...arguments)
1618
+ }
1051
1619
  ee.save = function() {
1052
1620
  return pmq(save, ...arguments)
1053
1621
  }
@@ -1060,7 +1628,9 @@ function WOrmReladb(opt = {}) {
1060
1628
  }
1061
1629
  else {
1062
1630
  ee.select = select
1631
+ ee.selectByPk = selectByPk
1063
1632
  ee.insert = insert
1633
+ ee.insertBulk = insertBulk
1064
1634
  ee.save = save
1065
1635
  ee.del = del
1066
1636
  ee.delAll = delAll