w-orm-lmdb 1.0.19 → 1.0.20

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.
@@ -28,13 +28,15 @@
28
28
 
29
29
  ### T2 回傳型別與鍵集合固定
30
30
 
31
- 同一函數不論走哪條路徑,回傳之**型別與鍵集合完全相同**。呼叫端不得需要先判斷某個鍵是否存在。
31
+ 回傳之**型別與鍵集合**由「函數」與「呼叫端明確給定之 option 取值」唯一決定;同一組合下不論走哪條內部路徑、不論數據內容為何,型別與鍵集合完全相同。呼叫端不得需要先判斷某個鍵是否存在。
32
32
 
33
33
  - 計數欄位(`n`、`nInserted`、`nModified`、`nDeleted`)在該函數的規格表中一旦列出即**恆出現**,無對應行為時填 `0`,不得省略。
34
34
  - 逐筆函數(`save`、`del`)恆回傳與輸入**等長**之陣列,即使輸入為單一物件亦回傳長度為 `1` 之陣列。
35
- - 整批函數(`insert`、`insertBulk`、`delAll`)恆回傳單一物件。
35
+ - 整批函數(`insert`、`insertBulk`、`delAll`)恆回傳單一物件;惟 `insert` 於 `option.returnList` 開啟時改回逐筆陣列,見該函數規格。
36
36
  - 唯一的例外是 `err`:僅在該筆 `ok` 為 `0` 時出現,`ok` 為 `1` 時不得出現。
37
37
 
38
+ option 對回傳形式之切換須為**靜態**:呼叫點寫死取值即知回傳形狀,不得設計成依執行結果或數據內容而變。共用之結果處理程式碼不得跨「不同 option 取值之呼叫點」混用——兩種取值即兩份契約。
39
+
38
40
  ### T3 `n` 之定義
39
41
 
40
42
  `n` 為**本次操作於資料庫端所命中或涉及之筆數**。逐函數定義如下,跨套件不得有第二種解讀:
@@ -49,6 +51,8 @@
49
51
 
50
52
  `n` 不得取全表筆數,不得為與結果無關之常數。
51
53
 
54
+ `insert` 於 `option.returnList` 開啟時之逐筆元素,其 `n` 恆為 `1`——該筆主鍵或為命中既有、或經插入而產生,對齊本表 `save`(逐筆)之定義;資訊由 `nInserted` 承載。
55
+
52
56
  ### T4 `ok` 與錯誤處置
53
57
 
54
58
  `ok` 僅有兩值:`1` 成功、`0` 該筆失敗。
@@ -56,10 +60,12 @@
56
60
  - 成功路徑一律 `ok: 1`。**不得**由驅動層之確認旗標(如 MongoDB 之 `acknowledged`、SQL driver 之連線狀態)直接推導——該類旗標會產生沒有錯誤訊息的 `ok: 0`,呼叫端無從處理。若確實需要反映驅動層的未確認狀態,須將其視為該筆失敗,回 `ok: 0` 並附 `err`。
57
61
  - `ok: 0` 僅出現於逐筆函數(`save`、`del`),且**必附 `err` 字串**說明原因。
58
62
  - 單筆失敗**不中斷整批**:其餘筆數照常處理,整批仍 resolve,該筆以 `ok: 0` 回報。
59
- - 整批性錯誤(連線失敗、參數型別錯誤、資料表不存在、權限不足等)以 `Promise.reject` 拋出,不進入逐筆結果。
63
+ - 整批性錯誤(連線失敗、實例已關閉、參數型別錯誤、資料表不存在、權限不足等)以 `Promise.reject` 拋出,不進入逐筆結果。
60
64
 
61
65
  判別「該筆失敗」與「整批性錯誤」的原則:錯誤只影響該筆資料者為前者,影響後續所有筆數者為後者。
62
66
 
67
+ **實例已關閉屬整批性錯誤,且須於各函數入口快速失敗。** 關閉為終態,後續操作必然失敗,故不得空轉等待至逾時,亦不得以正常空結果(`null`、未命中、空陣列)回應——後者使「已關閉」與「查無資料」同形,去重類呼叫端將把每筆判為未存在而靜默 fail-open(整批重送下游昂貴動作),此為該類機制最不能發生的失效方向。
68
+
63
69
  ### T5 輸入無效之處置
64
70
 
65
71
  「輸入無效」指傳入之 `data` 既非有效物件亦非有效陣列。此時不視為錯誤,回傳空結果:
@@ -67,6 +73,7 @@
67
73
  | 函數 | 回傳 |
68
74
  |---|---|
69
75
  | `insert` | `{ n: 0, nInserted: 0, ok: 1 }` |
76
+ | `insert`(`returnList` 開啟) | `[]` |
70
77
  | `insertBulk` | `{ n: 0, nInserted: 0, ok: 1 }` |
71
78
  | `save` | `[]` |
72
79
  | `del` | `[]` |
@@ -84,7 +91,7 @@
84
91
 
85
92
  `autoGenPk: false` 之定位為**依賴注入**:主鍵的產生規則改由呼叫端掌握(如採用外部發號器、以業務欄位組合、沿用上游系統既有識別碼),套件不介入。採用此設定後,**主鍵之唯一性、格式與是否與既有資料衝突,皆由呼叫端自負**;套件不做補救,亦不因主鍵不合預期而額外檢查或修正。
86
93
 
87
- `autoGenPk` 為建構層設定,**不得於 `insert`/`save` 之 `option` 逐次覆寫**。主鍵由誰產生是資料所有權的歸屬,屬整個資料表的政策;若逐次可改,同一資料表將混入兩種來源之主鍵而難以追溯。
94
+ `autoGenPk` 為建構層設定,**不得於各寫入函數之 `option` 逐次覆寫**。主鍵由誰產生是資料所有權的歸屬,屬整個資料表的政策;若逐次可改,同一資料表將混入兩種來源之主鍵而難以追溯。
88
95
 
89
96
  `autoGenPk` 為 `true` 時,自動產生之主鍵值須具足夠唯一性(如 UUID 或單調序列),不得採用可預期碰撞之來源。
90
97
 
@@ -98,7 +105,7 @@
98
105
 
99
106
  採用本例外者為**完全不提供該設定**,而非提供一個恆為 `false` 之設定——後者會讓呼叫端誤以為可切換,且徒增一個讀了文件仍不能用的選項。
100
107
 
101
- 此類套件之 `insert` 與 `save` 於輸入未帶有效主鍵時,一律以 `Promise.reject` 拋出整批性錯誤,行為等同 `autoGenPk` 為 `false`;主鍵之唯一性與格式同樣由呼叫端自負。
108
+ 此類套件之 `insert`、`insertBulk` 與 `save` 於輸入未帶有效主鍵時,一律以 `Promise.reject` 拋出整批性錯誤,行為等同 `autoGenPk` 為 `false`;主鍵之唯一性與格式同樣由呼叫端自負。
102
109
 
103
110
  採用本例外之套件,須於下方「各套件符合狀態」載明其主鍵欄位、所依據之情形與理由;未載明者一律適用 `autoGenPk` 預設為 `true` 之規定。
104
111
 
@@ -255,10 +262,12 @@ err 字串
255
262
 
256
263
  「命中」之判定基準須與 `insert`、`save`、`del` 內對既有數據之認定一致,不得出現 `selectByPk` 回傳物件而 `insert` 仍視為不存在之矛盾。
257
264
 
258
- ### insert(data) → `Promise<{ n, nInserted, ok }>`
265
+ ### insert(data, option) → `Promise<{ n, nInserted, ok } | Array<{ n, nInserted, ok }>>`
259
266
 
260
267
  **僅於主鍵不存在時寫入,已存在者跳過且不覆寫。**
261
268
 
269
+ 預設(`option.returnList` 未給或為 `false`)回傳單一聚合物件:
270
+
262
271
  | 欄位 | 值 |
263
272
  |---|---|
264
273
  | `n` | 輸入筆數 |
@@ -270,6 +279,25 @@ err 字串
270
279
  - 須符合 T7 之原子性要求。
271
280
  - 輸入無效見 T5。
272
281
 
282
+ #### `option.returnList`(預設 `false`):改回逐筆結果
283
+
284
+ 聚合計數回答「有幾筆是新的」,回答不了「**是哪幾筆**」——而後者正是去重之產出物(下游僅對新資料執行昂貴動作)。缺少逐筆結果時,呼叫端唯一出路是把批次退化為單筆呼叫、以 `nInserted === 1` 反推,令套件之批次寫入優化完全失效。本選項將函數內部本就算出之逐筆判定交出來。
285
+
286
+ `returnList` 為 `true` 時,回傳**與輸入等長、保序**之逐筆陣列,元素沿用本規格逐筆結果之家族形狀:
287
+
288
+ | 情形 | `n` | `nInserted` | `ok` |
289
+ |---|---|---|---|
290
+ | 該筆已插入 | `1` | `1` | `1` |
291
+ | 該筆主鍵已存在而跳過(含同批重複之非首筆) | `1` | `0` | `1` |
292
+
293
+ - 元素之 `n` 恆為 `1`(見 T3),`ok` 恆為 `1`——`insert` 之任何錯誤皆屬整批性錯誤而 `reject`(T4 不變),故逐筆元素不出現 `ok: 0` 與 `err`。
294
+ - 不變式:陣列長度等於輸入筆數;`filter(v => v.nInserted === 1).length` 等於預設模式之 `nInserted`。
295
+ - 輸入無效時回傳 `[]`(對齊 `save`/`del` 之 T5 規定)。
296
+ - 事件依 T10:`change` 之 `res` 即本次實際回傳值。
297
+ - 選擇此形狀而非另創布林陣列欄位,係因「逐筆結果」於本規格已有既定形狀(`save`/`del` 之元素);沿用同一形狀,呼叫端得以同一段程式碼處理各函數之逐筆結果,且自單筆迴圈遷移至批次時判準(`nInserted === 1`)不變。
298
+
299
+ **其餘函數不提供本選項**:`save` 與 `del` 之回傳本即與輸入等長、保序之逐筆陣列,資訊已在(`rs[i].nInserted === 1` 即第 i 筆是否插入、`rs[i].nModified === 1` 即第 i 筆是否實際寫入),再加即為冗餘且無處可掛;`insertBulk` 成功時逐筆恆為已插入(零資訊量)、失敗時整批 `reject` 而無部分結果,兩種情況皆無資訊可回。
300
+
273
301
  ### insertBulk(data) → `Promise<{ n, nInserted, ok }>`
274
302
 
275
303
  批次插入,**全批視為一個單位**:全部插入成功,或一筆都不寫入。
@@ -386,6 +414,8 @@ select(find) → [ {...}, {...} ] 無符合為 []
386
414
  selectByPk(pk) → {...} | null
387
415
 
388
416
  insert(data) → { n, nInserted, ok }
417
+ insert(data, { returnList: true })
418
+ → [ { n, nInserted, ok }, ... ] 與輸入等長保序, 逐筆ok恆1
389
419
  insertBulk(data) → { n, nInserted, ok } 衝突即整批reject且不寫入任何一筆
390
420
  save(data, option) → [ { n, nInserted, nModified, ok }, ... ]
391
421
  del(data) → [ { n, nDeleted, ok }, ... ]
@@ -402,6 +432,7 @@ delAll(find) → { n, nDeleted, ok }
402
432
  | 要判斷什麼 | 看什麼 | 不要看什麼 |
403
433
  |---|---|---|
404
434
  | 這批有幾筆是新資料 | `insert` 之 `nInserted` | `n`(那是輸入筆數) |
435
+ | 這批裡**哪幾筆**是新資料 | `insert` 開啟 `returnList` 後逐筆之 `nInserted === 1` | 聚合之 `nInserted`(那只有數量沒有身分) |
405
436
  | 這批有沒有撞到既有主鍵 | `insertBulk` 是否 `reject` | `nInserted`(成功時恆等於 `n`,不帶額外資訊) |
406
437
  | 這筆是不是新資料 | `save` 之 `nInserted === 1` | `n`(命中即為 1,插入與更新皆是) |
407
438
  | 這筆內容有沒有實際寫入 | `save` 之 `nModified === 1` | `n` |
@@ -416,10 +447,10 @@ delAll(find) → { n, nDeleted, ok }
416
447
 
417
448
  新套件納入 `w-orm-*` 系列前,逐項確認:
418
449
 
419
- 1. 六個函數皆存在,單筆直讀函數依 T1 名為 `selectByPk`,主鍵欄位名已於函數註解與「各套件符合狀態」載明。
450
+ 1. 七個函數皆存在,單筆直讀函數依 T1 名為 `selectByPk`,主鍵欄位名已於函數註解與「各套件符合狀態」載明。
420
451
  2. `select` 恆回陣列且不含資料庫內部欄位,`selectByPk` 未命中回 `null`。
421
- 3. 每個函數之計數欄位恆出現,無對應行為時填 `0`;`err` 僅隨 `ok: 0` 出現。
422
- 4. `n` 依 T3 定義,四個函數各自的基準寫進函數註解。
452
+ 3. 每個函數之計數欄位恆出現,無對應行為時填 `0`;`err` 僅隨 `ok: 0` 出現。以 option 靜態切換回傳形式者(如 `insert` 之 `returnList`),同一取值下形狀恆定。
453
+ 4. `n` 依 T3 定義,五個函數各自的基準寫進函數註解。
423
454
  5. `insert` 與 `save` 符合 T7 原子性要求;若倚賴某項設定達成,該設定預設開啟,且 README 載明升級前提。採「條件寫入配合衝突偵測與重試」達成者,每次寫入皆為單一條件式原子語句、衝突可被偵測且重試能收斂,讀取結果未用於決定寫入內容,並已於「各套件符合狀態」載明後端之限制、衝突之偵測方式與重試上限。
424
455
  6. 成功路徑 `ok` 恆為 `1`,不由驅動層旗標推導;`ok: 0` 必附 `err`;單筆失敗不中斷整批。
425
456
  7. `save` 之「內容相同」採**合併後比對**,基準寫進註解。
@@ -429,7 +460,7 @@ delAll(find) → { n, nDeleted, ok }
429
460
  11. README 依 T8 宣告併發保證範圍,無法保證者載明後果、實測依據與迴避方式。
430
461
  12. 依 T10 發出 `change` 與 `error` 事件,參數形狀為 `(mode, data, res)` 與 `(mode, data, err)`;所採用之 `EventEmitter` 無「`'error'` 於無監聽者時拋出」之語義;每一處 `emit` 皆以 try/catch 包覆;正常結果不發出 `error`;事件所送出之資訊皆另有正規管道,移除全部事件後呼叫端仍能取得完整資訊。
431
462
  13. `insertBulk` 語義為「衝突即整批 reject 且不寫入任何一筆」而非 `insert` 之加速版,未以別名或轉呼叫實作;寫入於該後端會被拆為多次送出者已以交易包覆並驗證回滾,無交易可用而採補償動作者已載明其限制;實作方式與是否具效能優勢已於「各套件符合狀態」載明。
432
- 14. 測試須涵蓋:同批重複主鍵、主鍵不存在、合併後內容相同、只給部份欄位且值相同、`autoInsert` 兩種取值、`autoGenPk` 兩種取值(含 `false` 且未帶主鍵須 `reject`)、單筆失敗、未帶有效主鍵、`delAll` 帶條件且僅部份命中,以及**同一操作於有註冊與未註冊 `error` 監聽兩種情況下回傳值完全相同**。提供 `insertBulk` 者另須涵蓋:無衝突時 `nInserted` 等於 `n`、撞既有主鍵時整批 `reject`、同批重複主鍵時整批 `reject`,以及**失敗後資料表無任何新增**。
463
+ 14. 測試須涵蓋:同批重複主鍵、主鍵不存在、合併後內容相同、只給部份欄位且值相同、`autoInsert` 兩種取值、`autoGenPk` 兩種取值(含 `false` 且未帶主鍵須 `reject`)、單筆失敗、未帶有效主鍵、`delAll` 帶條件且僅部份命中,以及**同一操作於有註冊與未註冊 `error` 監聽兩種情況下回傳值完全相同**。提供 `insertBulk` 者另須涵蓋:無衝突時 `nInserted` 等於 `n`、撞既有主鍵時整批 `reject`、同批重複主鍵時整批 `reject`,以及**失敗後資料表無任何新增**。`insert` 之 `returnList` 另須涵蓋:兩種取值下之形狀、開啟時與輸入等長保序且對位正確、同批重複主鍵僅首筆 `nInserted` 為 `1`、`filter` 計數等於聚合模式之 `nInserted`、輸入無效回 `[]`、逐筆元素鍵集合恰為 `{n, nInserted, ok}`。
433
464
 
434
465
  ---
435
466
 
@@ -439,9 +470,9 @@ delAll(find) → { n, nDeleted, ok }
439
470
 
440
471
  主鍵欄位為 `id`,為無業務語義之識別碼。主鍵欄位目前**固定為 `id`,尚未支援由呼叫端指定**。
441
472
 
442
- 已符合 T1–T10 與六函數全部規格,無待處理項目。
473
+ 已符合 T1–T10 與七函數全部規格,無待處理項目。
443
474
 
444
- T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),本即無「`'error'` 於無監聽者時拋出」之語義,符合 T10.1 第 2 條且無須改動。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於六函數皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。
475
+ T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),本即無「`'error'` 於無監聽者時拋出」之語義,符合 T10.1 第 2 條且無須改動。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於七函數皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。
445
476
 
446
477
  `opt.autoGenPk` 預設為 `true`,以 `genIDSeq()` 產生主鍵值;為 `false` 時 `insert` 與 `save` 皆不補值,未帶有效主鍵者以 `Promise.reject` 拋出整批性錯誤。主鍵檢查於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`del` 不受此設定影響。
447
478
 
@@ -458,15 +489,21 @@ T8 已完成:README 已宣告單一行程內保證成立、跨行程因 `lmdb-
458
489
 
459
490
  **效能與 `insert` 無顯著差異**(實測 N=20000:`insert` 650ms、`childTransaction` 640ms、`transactionSync` 645ms),因本套件之 `insert` 本即以 `Promise.all` 一次送出全部條件寫入而非逐筆 await。提供本函數係為與其他套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫。
460
491
 
492
+ **`insert` 之 `option.returnList`:已實作。** 逐筆判定即 `Promise.all` 各 `ifNoExists` 之回傳值(與輸入等長、保序),開啟時僅改包裝為逐筆結果陣列而不摺成計數,零推導成本。同批重複主鍵者僅首筆 `nInserted` 為 `1`,與聚合模式之計數一致。
493
+
494
+ **close() 後之快速失敗:已實作**(T4「實例已關閉」條)。七函數入口共用 `procClosed` 守門,`close()` 後再操作一律於入口立即以整批性錯誤 `reject`(訊息明示 closed,並依 T10.3 發出 `error` 事件),不與「查無資料」同形;`waitOpen` 另有終態前置判斷作為縱深防禦(攔操作進行中被 close 之競態),並移除輪詢中之逐秒 stdout 輸出、等待參數收斂為 50×200ms(waitFun 預設 200×1s)。修正前之實測症狀:`select`/`selectByPk`/`delAll` 於 close 後空轉約 200 秒方 reject;`del` 靜默回「主鍵未命中」之正常結果(`{n:0, nDeleted:0, ok:1}`,fail-open);`save` 誤降為逐筆 `ok: 0` 之 resolve;`insertBulk` 之 `childTransaction` 於已關閉 env 上自 `setImmediate` 拋出未捕捉例外而使**行程崩潰**——皆由入口守門一併攔下。
495
+
461
496
  ### w-orm-mongodb
462
497
 
463
498
  主鍵欄位為 `id`,為無業務語義之識別碼。主鍵欄位目前**固定為 `id`,尚未支援由呼叫端指定**,已於類別註解、`selectByPk` 註解與 README 載明。
464
499
 
465
- 已符合 T1–T10 與六函數全部規格,無待處理項目。
500
+ 已符合 T1–T10 與七函數之全部規格;惟 insert 之 returnList 選項尚未實作,見下方待處理。
466
501
 
467
502
  T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),其於 `'error'` 無監聽者時僅回傳 `false` 而不拋出,符合 T10.1 第 2 條;Node 內建之 `events.EventEmitter` 具該拋出語義,已停止使用。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於六函數與四個 GridFS 專屬函數皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。內部查找函數 `_findGfs` 不自行發出 `error`,其 reject 由 `delGfs` 與 `delAllGfs` 之 catch 接住並於該處發出,以免重複。`selectByPkGfs` 之「查無檔案」於 catch 內以 `code` 為 `ENOENT` 判定為正常結果而不設 `isErr`,故不會誤發 `error`。
468
503
 
469
- `opt.autoGenPk` 預設為 `true`,以 `genIDSeq()` 產生主鍵值——即 UUIDv7,符合 T6「如 UUID 或單調序列」,且其時間有序之特性令寫入主鍵唯一索引時之索引局部性優於純隨機字串;為 `false` 時 `insert`、`save` 與 `insertGfs` 皆不補值,未帶有效主鍵者以 `Promise.reject` 拋出整批性錯誤。主鍵檢查以共用之 `procPk` 於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`autoGenPk` 僅為建構層設定,`insert` 與 `save` 之 `option` 未提供覆寫。`del` 不受此設定影響。
504
+ `opt.autoGenPk` 預設為 `true`,以 `genIDSeq()` 產生主鍵值——即 UUIDv7,符合 T6「如 UUID 或單調序列」,且其時間有序之特性令寫入主鍵唯一索引時之索引局部性優於純隨機字串;為 `false` 時 `insert`、`insertBulk`、`save` 與 `insertGfs` 皆不補值,未帶有效主鍵者以 `Promise.reject` 拋出整批性錯誤。主鍵檢查以共用之 `procPk` 於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`autoGenPk` 僅為建構層設定,`insert`、`insertBulk` 與 `save` 之 `option` 未提供覆寫。`del` 不受此設定影響。
505
+
506
+ `save` 之重試上限為 3 次:其主體為單一 `updateOne` 配合 `upsert`,MongoDB 於單一語句內即可回報係插入或更新(`upsertedCount` 與 `matchedCount`),故不屬 T7 所稱「條件寫入配合衝突偵測與重試」之形式;該重試僅為處理「併發 upsert 於唯一索引上可能拋重複鍵錯誤」之既知競態,重試時該主鍵已存在故必走更新路徑而收斂。
470
507
 
471
508
  `save` 之「內容相同」判定即本規格之基準來源:由 MongoDB 於伺服器端將 `$set` 之待寫入物件合併進現值後與現值比對,未寫入即回 `modifiedCount` 為 `0`,比對與寫入於同一原子操作內完成,故不須預讀。
472
509
 
@@ -481,43 +518,74 @@ GridFS 之寫入係先寫 chunks 再寫 files 文件,故違反唯一索引時
481
518
  `save` 無 GridFS 對應函數:GridFS 無法於單一原子操作內取代既有內容,提供 `saveGfs` 將違反 T7,故不提供,更新以 `delGfs` 後再 `insertGfs` 完成。
482
519
 
483
520
 
484
- **`insertBulk`:待實作。** 全有全無之達成方式依部署而異:具 replica set 者以交易包覆 `insertMany({ ordered: true })`;standalone 無交易可用,須以補償動作達成——`insertMany({ ordered: false })` 後若 `insertedCount` 小於 `n`,由 `writeErrors` 取得衝突之索引,刪除本次已寫入之筆數後再 `reject`,並須於 README 載明「行程於補償途中中止則可能殘留」之限制。**不得以別名指向 `insert`**:如此則主鍵衝突時不會 `reject` 而是靜默跳過,呼叫端之錯誤前提於本套件上永遠不會浮現。本函數於本後端不會較 `insert` 快(`insert` 本即一次往返且計數精確),仍須提供以維持跨套件之可替換性。
521
+ **`insertBulk`:已實作。** 全有全無之達成方式依部署而異,由套件於執行期以 `hello` 回應判定拓樸(`setName` 存在或 `msg` `isdbgrid` 即表交易可用),每個實例判定一次並快取:
522
+
523
+ - **具 replica set 或分片叢集者以交易達成**——`session.withTransaction` 包覆 `insertMany({ ordered: true })`,任一筆衝突即整批回滾,無須補償動作。
524
+ - **standalone 無交易可用,以補償動作達成**——`insertMany({ ordered: false })` 失敗後刪除本次已寫入者再 `reject`。刪除之依據為**本次由驅動於送出前在用戶端所產生之 `_id`**,而非由 `writeErrors` 之索引反推輸入之主鍵值:後者於錯誤不帶 `writeErrors` 時(如網路中斷)會把全部輸入主鍵當成本次寫入而刪除,其中已存在者屬呼叫前既有資料,將造成資料損毀。已實測確認衝突筆所獲配之 `_id` 與庫內既有同主鍵者之 `_id` 不同,故按 `_id` 刪除不會誤及既有資料;刪除未寫入者為無操作,故不須精確得知哪幾筆已寫入。**限制**:行程若於補償途中中止則已寫入之部份可能殘留,已於 README 明白載明。
525
+
526
+ **未以別名或轉呼叫 `insert` 實作**:如此則主鍵衝突時不會 `reject` 而是靜默跳過,呼叫端之錯誤前提於本套件上永遠不會浮現。
527
+
528
+ **本函數於本後端不會較 `insert` 快**(`insert` 本即一次往返且計數精確),提供之理由為語義與跨套件之可替換性。
529
+
530
+ 兩條路徑皆已測試:standalone 之補償路徑見 `test/api-basic.test.mjs` 之 `insertBulk`,交易路徑見 `test/api-insertbulk-rs.test.mjs`。後者另起 `--replSet rs0` 之容器,並先斷言 `hello.setName` 存在以證明確走交易路徑,涵蓋撞既有主鍵、同批重複主鍵、200 筆批次末筆衝突之回滾,皆斷言失敗後筆數增量為 0 且既有數據未被改動。該容器不開啟認證,因 `--replSet` 配合 root 帳號須另備 keyFile 以供成員間內部認證,於測試無必要;連線採 `directConnection=true`,因容器內之成員位址為 `127.0.0.1:27017`,由宿主機依該位址無法連線,不可令驅動走成員探索。
531
+
532
+ **GridFS 不提供 `insertBulkGfs`**:依 §7 專屬函數不在本規格範圍內,§9 亦僅要求「若與六函數概念對應則參數形狀比照」而未要求補齊所有概念。GridFS 每筆寫入為 chunks 與 files 兩階段,全有全無所須清理之對象較一般資料表複雜,且無對應之使用場景。
533
+
534
+ 待處理:
535
+
536
+ | 項目 | 現況 | 規格 |
537
+ |---|---|---|
538
+ | `insert` 之 `option.returnList` | 未提供 | 依 insert 規格新增;逐筆判定可由 `insertMany({ ordered: false })` 之 `writeErrors` 索引反推(有索引者 `nInserted: 0`,其餘 `1`),與輸入等長保序 |
485
539
 
486
540
  ### w-orm-postgresql
487
541
 
488
- 主鍵欄位由建構時之 `opt.pk` 指定,預設為 `time`,**已支援由呼叫端指定**,`select` 以外之五函數皆以該欄位認定主鍵,已於類別註解、`selectByPk` 註解與 README 載明。
542
+ 主鍵欄位由建構時之 `opt.pk` 指定,預設為 `time`,**已支援由呼叫端指定**,`select` 以外之六函數皆以該欄位認定主鍵,已於類別註解與 `selectByPk` 註解載明。
489
543
 
490
- 已符合 T1–T10 與六函數全部規格,無待處理項目。
544
+ 已符合 T1–T10 與七函數之全部規格;惟 insert 之 returnList 選項尚未實作,見下方待處理。
491
545
 
492
- T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),其於 `'error'` 無監聽者時僅回傳 `false` 而不拋出,符合 T10.1 第 2 條;Node 內建之 `events.EventEmitter` 具該拋出語義,已停止使用。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於六函數與專屬函數 `createTable` 皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。`selectByPk` 之「主鍵未命中」與「主鍵值型別與欄位不符」皆不設 `isErr` 而回傳 `null`,故不會誤發 `error`;`insert` 全數已存在、`save` 合併後內容相同、`del` 主鍵未命中、`delAll` 條件無命中亦同。
546
+ T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),其於 `'error'` 無監聽者時僅回傳 `false` 而不拋出,符合 T10.1 第 2 條;Node 內建之 `events.EventEmitter` 具該拋出語義,已停止使用。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於七個函數與專屬函數 `createTable` 皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。`selectByPk` 之「主鍵未命中」與「主鍵值型別與欄位不符」皆不設 `isErr` 而回傳 `null`,故不會誤發 `error`;`insert` 全數已存在、`save` 合併後內容相同、`del` 主鍵未命中、`delAll` 條件無命中亦同。
493
547
 
494
548
  **不提供`autoGenPk`**,依 T6 之例外辦理,且兩種情形皆成立:
495
549
 
496
550
  1. 預設主鍵 `time` 承載觀測時間之業務意義。此類欄位若自動補值,將使「呼叫端漏給主鍵」靜默變成「以當下之值多寫入一筆」,且該筆無從與正常資料區辨。
497
551
  2. **主鍵欄位可由呼叫端指定,其型別則由資料表 schema 決定,套件並不知情**,故無從產生型別相容且符合 T6「須具足夠唯一性」之值:主鍵為 `TIMESTAMPTZ` 時可產生者僅有當下時間,同一毫秒內併發即碰撞,違反 T6「不得採用可預期碰撞之來源」;為 `TEXT` 時方適用隨機字串;為 `INTEGER` 時須倚賴資料庫端之 sequence。三者所需之產生策略互斥,而套件於寫入前無從得知係何者。
498
552
 
499
- 故縱使呼叫端將主鍵指定為無業務語義之欄位,本套件仍不提供補值,主鍵一律由呼叫端自備。`insert` 與 `save` 於輸入未帶有效主鍵值時,以 `Promise.reject` 拋出整批性錯誤;主鍵檢查於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`del` 不受此設定影響,未帶有效主鍵值者仍依 T6 回該筆 `ok: 0` + `err`。
553
+ 故縱使呼叫端將主鍵指定為無業務語義之欄位,本套件仍不提供補值,主鍵一律由呼叫端自備。`insert`、`insertBulk` 與 `save` 於輸入未帶有效主鍵值時,以 `Promise.reject` 拋出整批性錯誤;主鍵檢查於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`del` 不受此設定影響,未帶有效主鍵值者仍依 T6 回該筆 `ok: 0` + `err`。
500
554
 
501
- 主鍵值之有效性僅要求有給值而不限定型別,理由同上——主鍵欄位得由呼叫端指定而其型別隨欄位而異。型別與欄位不符者由 PostgreSQL 回報 `22P02` 或 `22007`,各函數依其規格分別處置:`selectByPk` 視為主鍵值無效而回傳 `null`,`insert` 與 `save` 為整批性錯誤而 `reject`,`del` 為該筆失敗而回 `ok: 0` + `err`。
555
+ 主鍵值之有效性僅要求有給值而不限定型別,理由同上——主鍵欄位得由呼叫端指定而其型別隨欄位而異。型別與欄位不符者由 PostgreSQL 回報 `22P02` 或 `22007`,各函數依其規格分別處置:`selectByPk` 視為主鍵值無效而回傳 `null`,`insert`、`insertBulk` 與 `save` 為整批性錯誤而 `reject`,`del` 為該筆失敗而回 `ok: 0` + `err`。
502
556
 
503
557
  `save` 之「內容相同」判定採合併後比對:以待寫入物件之非主鍵欄位淺層覆蓋現值後與現值比對。合併取淺層而非深層,係為與後端以 `EXCLUDED` 整欄取代之寫入行為一致——判定基準與實際寫入行為若不一致,`nModified` 即無法忠實反映是否真的寫入。快速路徑之預讀僅用於判斷是否略過寫入,寫入內容一律由原子語句自身決定。
504
558
 
505
- T7 之原子性以 `ON CONFLICT` 達成:`insert` 為單一 `INSERT ... ON CONFLICT (主鍵) DO NOTHING`,取 `rowCount` 為 `nInserted`;`save` 於 `autoInsert` 開啟時為單一 `INSERT ... ON CONFLICT (主鍵) DO UPDATE ... RETURNING (xmax = 0)`,以 `xmax` 區辨本語句插入與衝突後更新,關閉時為單一 `UPDATE ... WHERE 主鍵` 以免無中生有。其前提為主鍵具唯一約束,`createTable` 一律以 `PRIMARY KEY` 建立且無關閉選項;README Upgrading 已載明資料表於他處建立而缺該約束時之補建步驟,以及補建前須先清除重複主鍵值。
559
+ T7 之原子性以 `ON CONFLICT` 達成:`insert` `INSERT ... ON CONFLICT (主鍵) DO NOTHING`,取 `rowCount` 為 `nInserted`,筆數超出綁定參數上限者分批送出(見下方 `insertBulk` 之說明),因各筆之檢查與寫入本即逐筆獨立,分批不影響本項要求;`save` 於 `autoInsert` 開啟時為單一 `INSERT ... ON CONFLICT (主鍵) DO UPDATE ... RETURNING (xmax = 0)`,以 `xmax` 區辨本語句插入與衝突後更新,關閉時為單一 `UPDATE ... WHERE 主鍵` 以免無中生有。其前提為主鍵具唯一約束,`createTable` 一律以 `PRIMARY KEY` 建立且無關閉選項;資料表若於他處建立而缺該約束,`insert` `save` 將以 `there is no unique or exclusion constraint matching the ON CONFLICT specification` 拒絕,須先清除重複主鍵值後補建約束。
506
560
 
507
561
  T8 無須宣告:單一行程內併發與跨行程併發皆成立,故依 T8 未於 README 宣告。原子性全由 PostgreSQL 伺服器端提供,**寫入路徑不保有行程內狀態**且每次呼叫各自開啟連線,故兩種範圍於後端為同一情形。已測試依據含 2 個獨立行程對相同 20 個主鍵併發 `insert`、`nInserted` 總和為 20 且資料表僅 20 筆,以及 2 個行程對同一主鍵各寫入 5 個不同欄位、10 欄位全數保留。
508
562
 
509
563
  惟 `opt.useCache` 開啟時,`select` 與 `selectByPk` 之快取為行程內狀態:本行程之寫入會重設快取,他行程之寫入則不會,故跨行程下得讀到過期數據。此僅影響讀取之新鮮度,不影響上述寫入之原子性——快取不參與寫入路徑,`insert`、`save`、`del` 之判定與寫入一律由資料庫端完成。該選項預設為關閉,且已於類別註解載明適用於單程序操作。
510
564
 
511
- 本套件另有專屬函數 `createTable`,依 §7 不在本規格範圍內,其 `pk` 參數未給時採 `opt.pk`;因 T7 之原子性倚賴主鍵之唯一約束,該參數若給予與 `opt.pk` 不同之欄位,將建出其餘函數無法正確操作之資料表,已於函數註解與 README 載明。
565
+ 本套件另有專屬函數 `createTable`,依 §7 不在本規格範圍內,其 `pk` 參數未給時採 `opt.pk`;因 T7 之原子性倚賴主鍵之唯一約束,該參數若給予與 `opt.pk` 不同之欄位,將建出其餘函數無法正確操作之資料表,已於函數註解載明。
566
+
567
+
568
+ **`insertBulk`:已實作。** 全有全無以「單一多值 `INSERT`(不加 `ON CONFLICT`)配合交易包覆」達成:不加 `ON CONFLICT` 時,任一筆撞主鍵之唯一約束即整句失敗且不寫入任何一筆,同批含重複主鍵者亦於此被偵測為衝突,無須另行比對。未以 `insert` 之別名或轉呼叫實作,兩者之語句與衝突政策各自獨立。
569
+
570
+ PostgreSQL 之協定以 int16 記綁定參數個數,上限為 65535,故單一語句可送之筆數受欄位數所限(筆數 × 欄位數 ≤ 65535),超出者依 `floor(65535 / 欄位數)` 分批送出。分批時單語句之原子性已不足以維持「失敗即不留下部份寫入」,故各批一律以 `BEGIN`/`COMMIT` 包覆,任一批失敗即 `ROLLBACK`;不因批數而異,令單批與分批之保證來源一致。
512
571
 
572
+ **分批與交易包覆為 `insert` 與 `insertBulk` 共用**,收斂於內部之 `insertBatches`;衝突政策由參數區分而語句各自獨立——`insert` 添加 `ON CONFLICT DO NOTHING`,`insertBulk` 不添加。故 `insertBulk` 非 `insert` 之別名或轉呼叫,兩者之語義各自成立:已測試 21 欄之資料表送 3200 筆(分 2 批)而其中既有 1 筆主鍵者,`insert` 回 `nInserted` 為 3199 且資料表為 3200 筆,`insertBulk` 則整批 `reject` 且資料表之筆數增量為 0。
513
573
 
514
- **`insertBulk`:待實作。** 單一多值 `INSERT`(不加 `ON CONFLICT`)天然即為全有全無——任一筆撞主鍵約束則整句失敗且不寫入任何一筆,正合本函數語義。惟參數數量超出上限而須分批送出時,須以交易包覆。效能是否優於 `insert` 待實測。
574
+ **效能優於 `insert`**,且筆數愈多差距愈大。同一資料表無衝突插入之實測(Windows 11、PostgreSQL 17.11、Node v24.19.0,各 3 輪取平均):1000 筆 41ms→40ms、5000 筆 100ms→57ms、20000 筆 350ms→148ms。原因為 `insert` `ON CONFLICT DO NOTHING` 須對每列進行推測性插入,`insertBulk` 無此開銷;筆數少時該開銷不顯著,故兩者相當。
575
+
576
+ `insert` 於採用共用之分批機制前未分批,3 欄之資料表送 25000 筆即以 `bind message has 9464 parameter formats but 0 parameters` 拒絕且 0 筆寫入;改用後兩函數於同一輸入下皆分批完成 25000 筆。
577
+
578
+ 待處理:
579
+
580
+ | 項目 | 現況 | 規格 |
581
+ |---|---|---|
582
+ | `insert` 之 `option.returnList` | 未提供 | 依 insert 規格新增;逐筆判定可於 `INSERT ... ON CONFLICT DO NOTHING` 附加 `RETURNING 主鍵` 後映回輸入序(同批重複主鍵以首次出現者為插入),分批送出時逐批映回再串接 |
515
583
 
516
584
  ### w-orm-reladb
517
585
 
518
- 主鍵欄位由建構時之 `opt.pk` 指定,預設為 `id`,**已支援由呼叫端指定**,`select` 以外之五函數皆以該欄位認定主鍵,已於 `selectByPk` 註解與 README 載明。本套件經 sequelize 操作 mssql、sqlite、mysql、mariadb、postgres 五種後端。
586
+ 主鍵欄位由建構時之 `opt.pk` 指定,預設為 `id`,**已支援由呼叫端指定**,`select` 以外之六函數皆以該欄位認定主鍵,已於 `selectByPk` 註解與 README 載明。本套件經 sequelize 操作 mssql、sqlite、mysql、mariadb、postgres 五種後端。
519
587
 
520
- 已符合 T1–T10 與六函數全部規格,無待處理項目。
588
+ 已符合 T1–T10 與七函數之全部規格;惟 insert 之 returnList 選項尚未實作,見下方待處理。
521
589
 
522
590
  `opt.autoGenPk` 預設為 `true`,以 `genIDSeq()` 產生主鍵值——為 UUIDv7 格式之 36 碼字串,具單調遞增性,主鍵接近順序遞增可減少 B-tree 索引之頁面分裂;為 `false` 時 `insert` 與 `save` 皆不補值,未帶有效主鍵者以 `Promise.reject` 拋出整批性錯誤。主鍵檢查以共用之 `procPk` 於任何寫入之前一次完成,故整批 `reject` 時同批之有效筆數亦不會被寫入。`autoGenPk` 僅為建構層設定,`insert` 與 `save` 之 `option` 未提供覆寫。`del` 不受此設定影響。
523
591
 
@@ -537,11 +605,32 @@ T8 已完成:README 已宣告兩範圍之適用情形。**跨行程併發成
537
605
 
538
606
  註:此限制非受後端或其驅動層所限,而係本套件之連線管理所致——w-orm-mongodb 與 w-orm-postgresql 每次操作各自建立連線且不保有行程內狀態,故兩種範圍於後端為同一情形而無此區分。本套件因提供 `option.instance` 與 `option.transaction` 之共用連線擴充而保有行程內狀態,改為每次呼叫各自持有連線須一併調整該擴充,屬架構層級改動,暫以佇列預設開啟迴避。
539
607
 
540
- T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),其於 `'error'` 無監聽者時僅回傳 `false` 而不拋出,符合 T10.1 第 2 條;Node 內建之 `events.EventEmitter` 具該拋出語義,已停止使用。改用前曾實測其後果:呼叫端未註冊 `'error'` 監聽時,`save` 之逐筆失敗會被升級為整批 `reject`,違反 T4 之「單筆失敗不中斷整批」。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於六函數皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。`insert` 全數已存在、`save` 合併後內容相同、`del` 主鍵未命中、`delAll` 條件無命中、`selectByPk` 查無數據皆為正常結果而不設 `isErr`,故不會誤發 `error`。
608
+ T10 之實作:`EventEmitter` 採 `wsemi` 之 `evem()`(即 `eventemitter3`),其於 `'error'` 無監聽者時僅回傳 `false` 而不拋出,符合 T10.1 第 2 條;Node 內建之 `events.EventEmitter` 具該拋出語義,已停止使用。改用前曾實測其後果:呼叫端未註冊 `'error'` 監聽時,`save` 之逐筆失敗會被升級為整批 `reject`,違反 T4 之「單筆失敗不中斷整批」。全部事件收斂於共用之 `emitChange(mode, data, res)` 與 `emitError(mode, data, err)` 兩函數,try/catch 由該二處統一保證,`err` 一律經 `getErrMsg` 轉為字串。`change` 之逐筆函數以整批為單位發出一次,`save` 之逐筆插入另發 `mode` 為 `insert` 之事件;`error` 於七個函數皆發出,整批性錯誤於 `reject` 之前、逐筆失敗於該筆定案後各一次。`insert` 全數已存在、`save` 合併後內容相同、`del` 主鍵未命中、`delAll` 條件無命中、`selectByPk` 查無數據皆為正常結果而不設 `isErr`,故不會誤發 `error`。
541
609
 
542
- 本套件之六函數另有專屬之 `option.instance` 與 `option.transaction` 兩參數,供呼叫端共用連線實例與交易,未給時各函數自行初始化與關閉;此為向後相容之選配擴充,未給予時行為與規格所定完全相同,故不與六函數之語義衝突。另有專屬函數 `createStorage`、`genModelsByDB`、`genModelsByTabs`、`init`、`genTransaction`,依 §7 不在本規格範圍內,且皆不與六函數之概念對應。
610
+ 本套件之七函數另有專屬之 `option.instance` 與 `option.transaction` 兩參數,供呼叫端共用連線實例與交易,未給時各函數自行初始化與關閉;此為向後相容之選配擴充,未給予時行為與規格所定完全相同,故不與規格所定函數之語義衝突。另有專屬函數 `createStorage`、`genModelsByDB`、`genModelsByTabs`、`init`、`genTransaction`,依 §7 不在本規格範圍內,且皆不與規格所定函數之概念對應。
543
611
 
544
612
  建構時 `opt.url` 解析失敗一律 `throw`:其屬呼叫端未履行契約,且解析失敗後各函數皆無從運作;建構為同步而無從以 `Promise.reject` 回報,拋出即為 `reject` 之同步對應。原先僅 `console.log` 而回傳未綁定各函數之 `EventEmitter`,錯誤不經任何管道抵達呼叫端,呼叫端僅得到 `w.select is not a function` 之無關訊息,與本規格「錯誤不得靜默」之通則相違。
545
613
 
546
614
 
547
- **`insertBulk`:待實作,且效能效益最高。** 其後端之批次語句無從回報「本批有幾筆真的插入」,致 `insert` 只能逐筆寫入;改用批次語句後實測快上數量級。實作須以交易包覆——實測 mssql 因參數數量上限而由驅動層自動拆為多語句,1000 筆之批次於最後一筆撞主鍵時已有 946 筆落盤,未包交易即違反全有全無;包覆後兩後端於失敗時皆為 0 筆新增。
615
+ **`insertBulk`:已實作。** 全有全無以「單次 `bulkCreate`(不加任何跳過選項)配合交易包覆」達成:不加跳過選項時,任一筆撞主鍵之唯一約束即整句失敗,同批含重複主鍵者亦於此被偵測為衝突,無須另行比對。未以 `insert` 之別名或轉呼叫實作——`insert` 為逐筆 `create` 並攔截 `UniqueConstraintError` 以跳過既有主鍵,`insertBulk` 為單次 `bulkCreate` 且不攔截,兩者之語句與衝突政策各自獨立。
616
+
617
+ **交易包覆為必要而非優化。** 實測 mssql 因綁定參數數量上限(2100)而由驅動層自動將批次拆為多語句送出,1000 筆之批次於最後一筆撞主鍵時已有 **946 筆落盤**;sqlite 之批次為單一語句故天然原子。包覆後兩後端於失敗時皆為 0 筆新增,已以測試斷言。
618
+
619
+ **呼叫端已給 `option.transaction` 時改以巢狀交易(SAVEPOINT)包覆**,令回滾範圍限於本次呼叫。若逕自於呼叫端之交易內分批寫入而中途失敗,前段將留在該交易內未回滾,全有全無即不成立;亦不得回滾呼叫端之交易,那會連帶撤銷其先前之寫入。已實測 sqlite 與 mssql 之 SAVEPOINT 回滾範圍皆正確——外層交易先前之寫入保留、內層失敗之批次全數不存在,並以測試斷言。
620
+
621
+ **效能優於 `insert`,且筆數愈多差距愈大**(Windows 11、Node 24、sequelize 6、MSSQL 2022、SQLite 經 sqlite3 綁定):
622
+
623
+ | 筆數 | sqlite `insert` → `insertBulk` | mssql `insert` → `insertBulk` |
624
+ |---|---|---|
625
+ | 1000 | 5549 ms → 25 ms(226x) | 6802 ms → 190 ms(36x) |
626
+ | 5000 | 29825 ms → 50 ms(592x) | 44116 ms → 489 ms(90x) |
627
+
628
+ 差距之來源為 `insert` 須逐筆寫入方能回報精確之 `nInserted`(見上方 T7 之說明——本後端之批次語句無從回報「本批有幾筆真的插入」),`insertBulk` 因採全有全無而無此需求。
629
+
630
+ `opt.useEncryption` 開啟且後端為 sqlite 時不自開交易,因 `@journeyapps/sqlcipher` 不支援交易;該情形下全有全無倚賴 sqlite 單一批次語句之原子性。
631
+
632
+ 待處理:
633
+
634
+ | 項目 | 現況 | 規格 |
635
+ |---|---|---|
636
+ | `insert` 之 `option.returnList` | 未提供 | 依 insert 規格新增;本套件之 `insert` 本即逐筆 `create` 並以 `UniqueConstraintError` 辨識既有主鍵,逐筆判定現成,僅須改包裝為逐筆結果陣列 |
@@ -1,7 +0,0 @@
1
- /*!
2
- * req-mingo v1.0.19
3
- * (c) 2018-2021 yuda-lyu(semisphere)
4
- * Released under the MIT License.
5
- */
6
- !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o(require("mingo")):"function"==typeof define&&define.amd?define(["mingo"],o):(e="undefined"!=typeof globalThis?globalThis:e||self)["req-mingo"]=o(e.mingo)}(this,function(e){"use strict";function o(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}return o(e)});
7
- //# sourceMappingURL=req-mingo.umd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"req-mingo.umd.js","sources":["../src/reqMingo.js"],"sourcesContent":null,"names":["require$$0"],"mappings":";;;;;2XAAYA"}