w-orm-reladb 1.0.46 → 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.
- package/README.md +86 -50
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +758 -73
- package/docs/WOrmReladb.mjs.html +869 -299
- 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 +3 -3
- package/script.txt +0 -1
- package/src/WOrmReladb.mjs +867 -297
- package/test/api-mssql.test.mjs +4 -201
- package/test/api-sqlite.test.mjs +12 -206
- package/test/api-suite.mjs +729 -0
- package/worm.sqlite +0 -0
- package/test/api-fixtures.mjs +0 -48
package/README.md
CHANGED
|
@@ -11,6 +11,42 @@ An operator for relational database in nodejs.
|
|
|
11
11
|
## Documentation
|
|
12
12
|
To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-orm-reladb/WOrm.html).
|
|
13
13
|
|
|
14
|
+
### `insert` and `insertBulk`
|
|
15
|
+
|
|
16
|
+
Both write rows that do not yet exist, and both return `{ n, nInserted, ok }`. They differ in what happens on a conflict, so `insertBulk` is **not** a faster `insert`:
|
|
17
|
+
|
|
18
|
+
| Situation | `insert` | `insertBulk` |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Primary key already exists | that row is skipped, the batch still returns `ok: 1` | the whole call **rejects, and not a single row is written** |
|
|
21
|
+
| Duplicate primary keys inside one batch | only the first one counts toward `nInserted` | treated as a conflict, the whole call rejects |
|
|
22
|
+
| `nInserted` | rows actually inserted, `0 ≤ nInserted ≤ n` | equals `n` whenever the call succeeds |
|
|
23
|
+
| Use it for | ordinary writes against a table that may already hold data | bulk import where no conflict is expected |
|
|
24
|
+
|
|
25
|
+
With no conflict the two are indistinguishable, so you may swap them under that assumption — and if the assumption is wrong, `insertBulk` tells you by rejecting rather than silently skipping.
|
|
26
|
+
|
|
27
|
+
`insertBulk` is considerably faster here, because `insert` has to write row by row to report an exact `nInserted` (measured on this machine, Windows 11 / Node 24 / sequelize 6):
|
|
28
|
+
|
|
29
|
+
| Rows | sqlite `insert` → `insertBulk` | mssql `insert` → `insertBulk` |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| 1000 | 5549 ms → 25 ms (226x) | 6802 ms → 190 ms (36x) |
|
|
32
|
+
| 5000 | 29825 ms → 50 ms (592x) | 44116 ms → 489 ms (90x) |
|
|
33
|
+
|
|
34
|
+
The all-or-nothing guarantee is provided by a transaction, not by the batch statement alone: mssql splits a large batch into several statements because of its bind parameter limit, so without one an interrupted batch would leave earlier rows behind. When you pass `option.transaction`, a SAVEPOINT is used instead, so a failure rolls back only this call and leaves the rest of your transaction untouched.
|
|
35
|
+
|
|
36
|
+
### Concurrency
|
|
37
|
+
|
|
38
|
+
The scope of the atomicity guarantee, by range:
|
|
39
|
+
|
|
40
|
+
| Range | Guaranteed | Provided by |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Cross process — several processes against the same database | Yes | The database itself. `insert` and `save` write through single conditional statements, and the primary key unique constraint decides the winner. Independent of `opt.useStable`. |
|
|
43
|
+
| Single process — several parallel calls in one process | Yes **when `opt.useStable` is `true`** (the default) | A built-in queue that runs one operation at a time. |
|
|
44
|
+
| Single process, with `opt.useStable` set to `false` | **No** | — |
|
|
45
|
+
|
|
46
|
+
**Do not issue parallel calls from one process while `opt.useStable` is `false`.** Each instance holds one shared connection, opened at the start of a call and closed at its end, so a second call running in parallel closes the connection the first one is still using. Measured on 30 parallel calls in a single process: sqlite raised `SQLITE_MISUSE: Database handle is closed` and rejected 12 of the batches, mssql reported `ConnectionManager.getConnection was called after the connection manager was closed!` as a per row failure; both ended with 29 of the 30 rows written, so writes are silently lost. Keep the default `true`, or serialize the calls yourself (for example `await` them one by one).
|
|
47
|
+
|
|
48
|
+
Cross process was measured with 2 independent processes against the same 20 primary keys: `nInserted` summed to exactly 20 with 20 rows in the table, and with each process writing a different column to those keys all 40 operations returned `ok: 1` with both columns preserved on all 20 rows. sqlite was repeated over 6 rounds to rule out timing dependent `SQLITE_BUSY`. Platform: Windows 11, Node 24, sequelize 6, MSSQL 2022.
|
|
49
|
+
|
|
14
50
|
## Before Installation
|
|
15
51
|
> If you need to use encrypted sqlite, you need to manually install `@journeyapps/sqlcipher`, as follows:
|
|
16
52
|
>1. Open visual studio code by system administrator.
|
|
@@ -42,7 +78,7 @@ let opt = {
|
|
|
42
78
|
cl: 'users',
|
|
43
79
|
fdModels: './models',
|
|
44
80
|
// modelType: 'json',
|
|
45
|
-
//
|
|
81
|
+
// autoGenPk: false,
|
|
46
82
|
}
|
|
47
83
|
|
|
48
84
|
let rs = [
|
|
@@ -106,8 +142,8 @@ async function test() {
|
|
|
106
142
|
w.on('change', function(mode, data, res) {
|
|
107
143
|
console.log('change', mode)
|
|
108
144
|
})
|
|
109
|
-
w.on('error', function(err) {
|
|
110
|
-
console.log('error', err)
|
|
145
|
+
w.on('error', function(mode, data, err) {
|
|
146
|
+
console.log('error', mode, err)
|
|
111
147
|
})
|
|
112
148
|
|
|
113
149
|
|
|
@@ -196,10 +232,10 @@ test()
|
|
|
196
232
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
197
233
|
// change save
|
|
198
234
|
// save then [
|
|
199
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
200
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
201
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
202
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
235
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
236
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
237
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
238
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
203
239
|
// ]
|
|
204
240
|
// select all [
|
|
205
241
|
// { id: 'id-peter', name: 'peter(modify)', value: 123 },
|
|
@@ -243,7 +279,7 @@ let opt = {
|
|
|
243
279
|
cl: 'users',
|
|
244
280
|
fdModels: './models',
|
|
245
281
|
// modelType: 'json',
|
|
246
|
-
//
|
|
282
|
+
// autoGenPk: false,
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
let rs = [
|
|
@@ -307,8 +343,8 @@ async function testCommit() {
|
|
|
307
343
|
w.on('change', function(mode, data, res) {
|
|
308
344
|
console.log('change', mode)
|
|
309
345
|
})
|
|
310
|
-
w.on('error', function(err) {
|
|
311
|
-
console.log('error', err)
|
|
346
|
+
w.on('error', function(mode, data, err) {
|
|
347
|
+
console.log('error', mode, err)
|
|
312
348
|
})
|
|
313
349
|
|
|
314
350
|
|
|
@@ -424,10 +460,10 @@ testCommit()
|
|
|
424
460
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
425
461
|
// change save
|
|
426
462
|
// save then [
|
|
427
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
428
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
429
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
430
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
463
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
464
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
465
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
466
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
431
467
|
// ]
|
|
432
468
|
// change del
|
|
433
469
|
// del then [
|
|
@@ -460,7 +496,7 @@ let opt = {
|
|
|
460
496
|
cl: 'users',
|
|
461
497
|
fdModels: './models',
|
|
462
498
|
// modelType: 'json',
|
|
463
|
-
//
|
|
499
|
+
// autoGenPk: false,
|
|
464
500
|
}
|
|
465
501
|
|
|
466
502
|
let rs = [
|
|
@@ -524,8 +560,8 @@ async function testRollback() {
|
|
|
524
560
|
w.on('change', function(mode, data, res) {
|
|
525
561
|
console.log('change', mode)
|
|
526
562
|
})
|
|
527
|
-
w.on('error', function(err) {
|
|
528
|
-
console.log('error', err)
|
|
563
|
+
w.on('error', function(mode, data, err) {
|
|
564
|
+
console.log('error', mode, err)
|
|
529
565
|
})
|
|
530
566
|
|
|
531
567
|
|
|
@@ -638,10 +674,10 @@ testRollback()
|
|
|
638
674
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
639
675
|
// change save
|
|
640
676
|
// save then [
|
|
641
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
642
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
643
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
644
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
677
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
678
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
679
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
680
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
645
681
|
// ]
|
|
646
682
|
// change del
|
|
647
683
|
// del then [
|
|
@@ -672,7 +708,7 @@ let opt = {
|
|
|
672
708
|
cl: 'users',
|
|
673
709
|
fdModels: './models',
|
|
674
710
|
// modelType: 'json',
|
|
675
|
-
//
|
|
711
|
+
// autoGenPk: false,
|
|
676
712
|
storage: './worm.sqlite',
|
|
677
713
|
}
|
|
678
714
|
|
|
@@ -743,8 +779,8 @@ async function test() {
|
|
|
743
779
|
w.on('change', function(mode, data, res) {
|
|
744
780
|
console.log('change', mode)
|
|
745
781
|
})
|
|
746
|
-
w.on('error', function(err) {
|
|
747
|
-
console.log('error', err)
|
|
782
|
+
w.on('error', function(mode, data, err) {
|
|
783
|
+
console.log('error', mode, err)
|
|
748
784
|
})
|
|
749
785
|
|
|
750
786
|
|
|
@@ -833,10 +869,10 @@ test()
|
|
|
833
869
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
834
870
|
// change save
|
|
835
871
|
// save then [
|
|
836
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
837
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
838
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
839
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
872
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
873
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
874
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
875
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
840
876
|
// ]
|
|
841
877
|
// select all [
|
|
842
878
|
// { id: 'id-peter', name: 'peter(modify)', value: 123 },
|
|
@@ -881,7 +917,7 @@ let opt = {
|
|
|
881
917
|
cl: 'users',
|
|
882
918
|
fdModels: './models',
|
|
883
919
|
// modelType: 'json',
|
|
884
|
-
//
|
|
920
|
+
// autoGenPk: false,
|
|
885
921
|
storage: './worm.sqlite',
|
|
886
922
|
}
|
|
887
923
|
|
|
@@ -951,8 +987,8 @@ async function testCommit() {
|
|
|
951
987
|
w.on('change', function(mode, data, res) {
|
|
952
988
|
console.log('change', mode)
|
|
953
989
|
})
|
|
954
|
-
w.on('error', function(err) {
|
|
955
|
-
console.log('error', err)
|
|
990
|
+
w.on('error', function(mode, data, err) {
|
|
991
|
+
console.log('error', mode, err)
|
|
956
992
|
})
|
|
957
993
|
|
|
958
994
|
|
|
@@ -1068,10 +1104,10 @@ testCommit()
|
|
|
1068
1104
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
1069
1105
|
// change save
|
|
1070
1106
|
// save then [
|
|
1071
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1072
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1073
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1074
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
1107
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1108
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1109
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1110
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
1075
1111
|
// ]
|
|
1076
1112
|
// change del
|
|
1077
1113
|
// del then [
|
|
@@ -1105,7 +1141,7 @@ let opt = {
|
|
|
1105
1141
|
cl: 'users',
|
|
1106
1142
|
fdModels: './models',
|
|
1107
1143
|
// modelType: 'json',
|
|
1108
|
-
//
|
|
1144
|
+
// autoGenPk: false,
|
|
1109
1145
|
storage: './worm.sqlite',
|
|
1110
1146
|
}
|
|
1111
1147
|
|
|
@@ -1175,8 +1211,8 @@ async function testRollback() {
|
|
|
1175
1211
|
w.on('change', function(mode, data, res) {
|
|
1176
1212
|
console.log('change', mode)
|
|
1177
1213
|
})
|
|
1178
|
-
w.on('error', function(err) {
|
|
1179
|
-
console.log('error', err)
|
|
1214
|
+
w.on('error', function(mode, data, err) {
|
|
1215
|
+
console.log('error', mode, err)
|
|
1180
1216
|
})
|
|
1181
1217
|
|
|
1182
1218
|
|
|
@@ -1289,10 +1325,10 @@ testRollback()
|
|
|
1289
1325
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
1290
1326
|
// change save
|
|
1291
1327
|
// save then [
|
|
1292
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1293
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1294
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1295
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
1328
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1329
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1330
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1331
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
1296
1332
|
// ]
|
|
1297
1333
|
// change del
|
|
1298
1334
|
// del then [
|
|
@@ -1323,7 +1359,7 @@ let opt = {
|
|
|
1323
1359
|
cl: 'users',
|
|
1324
1360
|
fdModels: './models',
|
|
1325
1361
|
// modelType: 'json',
|
|
1326
|
-
//
|
|
1362
|
+
// autoGenPk: false,
|
|
1327
1363
|
storage: './worm.sqlite',
|
|
1328
1364
|
useEncryption: true,
|
|
1329
1365
|
}
|
|
@@ -1401,8 +1437,8 @@ async function test() {
|
|
|
1401
1437
|
w.on('change', function(mode, data, res) {
|
|
1402
1438
|
console.log('change', mode)
|
|
1403
1439
|
})
|
|
1404
|
-
w.on('error', function(err) {
|
|
1405
|
-
console.log('error', err)
|
|
1440
|
+
w.on('error', function(mode, data, err) {
|
|
1441
|
+
console.log('error', mode, err)
|
|
1406
1442
|
})
|
|
1407
1443
|
|
|
1408
1444
|
|
|
@@ -1491,10 +1527,10 @@ test()
|
|
|
1491
1527
|
// insert then { n: 3, nInserted: 3, ok: 1 }
|
|
1492
1528
|
// change save
|
|
1493
1529
|
// save then [
|
|
1494
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1495
|
-
// { n: 1, nModified: 1, ok: 1 },
|
|
1496
|
-
// { n: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1497
|
-
// { n: 1, nInserted: 1, ok: 1 } //autoInsert=true
|
|
1530
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1531
|
+
// { n: 1, nInserted: 0, nModified: 1, ok: 1 },
|
|
1532
|
+
// { n: 0, nInserted: 0, nModified: 0, ok: 1 } //autoInsert=false
|
|
1533
|
+
// { n: 1, nInserted: 1, nModified: 0, ok: 1 } //autoInsert=true
|
|
1498
1534
|
// ]
|
|
1499
1535
|
// select all [
|
|
1500
1536
|
// { id: 'id-peter', name: 'peter(modify)', value: 123 },
|
|
@@ -1538,7 +1574,7 @@ let opt = {
|
|
|
1538
1574
|
cl: 'users',
|
|
1539
1575
|
fdModels: './models',
|
|
1540
1576
|
// modelType: 'json',
|
|
1541
|
-
//
|
|
1577
|
+
// autoGenPk: false,
|
|
1542
1578
|
}
|
|
1543
1579
|
|
|
1544
1580
|
let fd = opt.fdModels
|