w-orm-reladb 1.1.5 → 1.1.7
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 +25 -3
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +39 -21
- package/docs/WOrmReladb.mjs.html +171 -110
- package/docs/cvFind.mjs.html +861 -0
- package/docs/global.html +1716 -0
- package/docs/importModels.mjs.html +255 -0
- package/docs/index.html +2 -2
- package/g-mssql.mjs +1 -1
- package/g-sqlite-encryption.mjs +1 -1
- package/g-sqlite.mjs +1 -1
- package/models/tcond.js +74 -0
- package/models/tpkdec.js +20 -0
- package/models/tpkint.js +20 -0
- package/package.json +4 -4
- package/src/WOrmReladb.mjs +169 -108
- package/src/cvFind.mjs +789 -0
- package/src/importModels.mjs +180 -114
- package/test/api-models.test.mjs +216 -0
- package/test/api-suite.mjs +492 -3
- package/test/tools/modelsWorker.mjs +35 -0
- package/test/unit-cvfind.test.mjs +393 -0
- package/src/modifyModel.mjs +0 -113
package/src/importModels.mjs
CHANGED
|
@@ -1,117 +1,183 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import JSON5 from 'json5'
|
|
4
|
-
import Sequelize from 'sequelize'
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import JSON5 from 'json5'
|
|
4
|
+
import Sequelize from 'sequelize'
|
|
5
5
|
import each from 'lodash-es/each.js'
|
|
6
6
|
import get from 'lodash-es/get.js'
|
|
7
7
|
import isBoolean from 'lodash-es/isBoolean.js'
|
|
8
|
-
import genPm from 'wsemi/src/genPm.mjs'
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
let
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
8
|
+
import genPm from 'wsemi/src/genPm.mjs'
|
|
9
|
+
import requireModel from './requireModel.js'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
//匯入資料表設定檔(models)
|
|
13
|
+
//
|
|
14
|
+
//model內之id欄位須為主鍵, 否則sequelize拒絕匯入(model.js: A column called 'id' was added ... but not marked with 'primaryKey: true')
|
|
15
|
+
//原版以modifyModel於[每一次]匯入時無條件重寫呼叫端之model原始檔(逐行trim、刪空行並改為LF)以補上主鍵設定,
|
|
16
|
+
//而本套件每次操作(含select、selectByPk)皆匯入一次, 故讀取函數亦改變可被觀察之狀態(規格T9),
|
|
17
|
+
//且多行程併發時一方正截斷檔案而另一方讀取, 即讀到空內容並寫回空檔而[永久損毀](已實機重現: 4行程併發, 4/4輪model檔被截為0位元組)
|
|
18
|
+
//
|
|
19
|
+
//本版改於記憶體內補上主鍵設定, 一律不寫model檔:
|
|
20
|
+
// js格式於呼叫model之定義函數期間暫時包裝sequelize.define, 於定義前補上id之primaryKey
|
|
21
|
+
// json格式於解析後、define前補上
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 令欄位定義之id為主鍵, 不改動傳入之物件
|
|
26
|
+
* 註: 僅於model完全沒有主鍵設定而有id欄時補上(sequelize於此情形拒絕匯入);
|
|
27
|
+
* model已有其他主鍵時不改動, 否則成為(既有主鍵, id)之複合主鍵而使主鍵去重失效(原版modifyModel有此缺陷)
|
|
28
|
+
*
|
|
29
|
+
* @ignore
|
|
30
|
+
* @param {Object} attrs 輸入欄位定義物件
|
|
31
|
+
* @returns {Object} 回傳欄位定義物件
|
|
32
|
+
*/
|
|
33
|
+
function patchIdPk(attrs) {
|
|
34
|
+
|
|
35
|
+
//check
|
|
36
|
+
if (!attrs || !Object.prototype.hasOwnProperty.call(attrs, 'id')) {
|
|
37
|
+
return attrs
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//check, 已有任一主鍵設定者不改動
|
|
41
|
+
let hasPk = Object.keys(attrs).some((k) => get(attrs[k], 'primaryKey') === true)
|
|
42
|
+
if (hasPk) {
|
|
43
|
+
return attrs
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//id
|
|
47
|
+
let id = attrs.id
|
|
48
|
+
|
|
49
|
+
//idNew, id可為欄位設定物件或直接為型別
|
|
50
|
+
let idNew = Object.prototype.toString.call(id) === '[object Object]' && get(id, 'type') !== undefined
|
|
51
|
+
? { ...id, primaryKey: true }
|
|
52
|
+
: { type: id, primaryKey: true }
|
|
53
|
+
|
|
54
|
+
return { ...attrs, id: idNew }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 讀取js格式之model
|
|
60
|
+
* 註: 於呼叫model之定義函數期間暫時以實例自有屬性包裝define, 結束後刪除該屬性即回復原型上之define
|
|
61
|
+
*
|
|
62
|
+
* @ignore
|
|
63
|
+
*/
|
|
64
|
+
function readJsModel(fn, sequelize) {
|
|
65
|
+
let define = sequelize.define
|
|
66
|
+
sequelize.define = function(name, attrs, options) {
|
|
67
|
+
return define.call(sequelize, name, patchIdPk(attrs), options)
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return requireModel(fn, sequelize, Sequelize.DataTypes)
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
delete sequelize.define
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 讀取json格式之model
|
|
80
|
+
*
|
|
81
|
+
* @ignore
|
|
82
|
+
*/
|
|
83
|
+
function readJsonModel(fn, sequelize) {
|
|
84
|
+
|
|
85
|
+
//s
|
|
86
|
+
let j = fs.readFileSync(fn, 'utf8')
|
|
87
|
+
let s = JSON5.parse(j)
|
|
88
|
+
|
|
89
|
+
//抽換DataTypes, 例如將'DataTypes.TEXT'改為DataTypes.TEXT
|
|
90
|
+
each(s.fields, (v) => {
|
|
91
|
+
v.type = get(Sequelize, v.type, null)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
//model, 使用define產生model, 定義前於記憶體內補上id之主鍵設定
|
|
95
|
+
let model = sequelize.define(s.table, patchIdPk(s.fields), s.options)
|
|
96
|
+
|
|
97
|
+
return model
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 匯入資料表設定檔
|
|
103
|
+
*
|
|
104
|
+
* 註: 不寫入model檔, id之主鍵設定於記憶體內補上
|
|
105
|
+
*
|
|
106
|
+
* @param {String} fdModels 輸入models所在資料夾字串
|
|
107
|
+
* @param {Object} sequelize 輸入sequelize實例
|
|
108
|
+
* @param {String} name 輸入資料表名稱字串
|
|
109
|
+
* @param {Object} [opt={}] 輸入設定物件
|
|
110
|
+
* @param {String} [opt.type='js'] 輸入資料表設定檔類型字串,可有'js'、'json'
|
|
111
|
+
* @param {Boolean} [opt.sync=false] 輸入是否將models資料同步至資料庫
|
|
112
|
+
* @returns {Promise} 回傳Promise,resolve回傳models物件,reject回傳錯誤訊息
|
|
113
|
+
*/
|
|
114
|
+
async function importModels(fdModels, sequelize, name, opt = {}) {
|
|
115
|
+
|
|
116
|
+
//type
|
|
117
|
+
let type = get(opt, 'type')
|
|
118
|
+
if (type !== 'js' && type !== 'json') {
|
|
119
|
+
type = 'js'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//sync
|
|
123
|
+
let sync = get(opt, 'sync')
|
|
124
|
+
if (!isBoolean(sync)) {
|
|
125
|
+
sync = false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//pm
|
|
129
|
+
let pm = genPm()
|
|
130
|
+
|
|
131
|
+
//file
|
|
132
|
+
let file = `${name}.${type}`
|
|
133
|
+
|
|
134
|
+
//fparent
|
|
135
|
+
let fparent = path.resolve(fdModels) + path.sep
|
|
136
|
+
|
|
137
|
+
//fn
|
|
138
|
+
let fn = fparent + file
|
|
139
|
+
|
|
140
|
+
//import
|
|
141
|
+
let model
|
|
142
|
+
try {
|
|
143
|
+
if (type === 'js') {
|
|
144
|
+
model = readJsModel(fn, sequelize)
|
|
145
|
+
}
|
|
146
|
+
else if (type === 'json') {
|
|
147
|
+
model = readJsonModel(fn, sequelize)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
pm.reject(err)
|
|
152
|
+
return pm
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//sync
|
|
156
|
+
try {
|
|
157
|
+
if (sync) {
|
|
158
|
+
await model.sync() //{ force: true }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
pm.reject(err)
|
|
163
|
+
return pm
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//models
|
|
167
|
+
let models = {}
|
|
168
|
+
try {
|
|
169
|
+
models[model.name] = model
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
pm.reject(err)
|
|
173
|
+
return pm
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//resolve
|
|
177
|
+
pm.resolve(models)
|
|
178
|
+
|
|
179
|
+
return pm
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
export default importModels
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import { spawn } from 'child_process'
|
|
5
|
+
import wo from '../src/WOrmReladb.mjs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
//rulebook R9: 各操作不得改寫呼叫端之model檔(規格T9讀取函數不得有副作用); 需補主鍵設定者只補該處、保留行尾並以原子寫入完成
|
|
9
|
+
//僅以sqlite驗證, 因model檔之處理與資料庫種類無關
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
let fd = './test/_tmp/api-models'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
//genModel, 含縮排、空行與CRLF之model, 修正前之modifyModel會逐行trim、刪空行並改為LF而改寫
|
|
16
|
+
let genModel = (withPk) => {
|
|
17
|
+
return [
|
|
18
|
+
'/* jshint indent: 1 */',
|
|
19
|
+
'module.exports = function(sequelize, DataTypes) {',
|
|
20
|
+
' return sequelize.define(\'users\', {',
|
|
21
|
+
' \'id\': {',
|
|
22
|
+
' type: DataTypes.STRING,',
|
|
23
|
+
' allowNull: false,',
|
|
24
|
+
withPk ? ' primaryKey: true,' : ' autoIncrement: false,',
|
|
25
|
+
' },',
|
|
26
|
+
'',
|
|
27
|
+
' \'name\': {',
|
|
28
|
+
' type: DataTypes.TEXT,',
|
|
29
|
+
' allowNull: true',
|
|
30
|
+
' },',
|
|
31
|
+
' \'value\': {',
|
|
32
|
+
' type: DataTypes.FLOAT,',
|
|
33
|
+
' allowNull: true',
|
|
34
|
+
' }',
|
|
35
|
+
' }, {',
|
|
36
|
+
' tableName: \'users\'',
|
|
37
|
+
' })',
|
|
38
|
+
'}',
|
|
39
|
+
'',
|
|
40
|
+
].join('\r\n')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
let runWorker = (fdModels, storage, n) => {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
let p = spawn(process.execPath, ['test/tools/modelsWorker.mjs', fdModels, storage, String(n)], { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
47
|
+
let s = ''
|
|
48
|
+
p.stdout.on('data', (d) => {
|
|
49
|
+
s += d
|
|
50
|
+
})
|
|
51
|
+
p.stderr.on('data', () => {})
|
|
52
|
+
p.on('close', () => {
|
|
53
|
+
let line = s.trim().split('\n').filter((v) => v.startsWith('{')).pop()
|
|
54
|
+
resolve(line ? JSON.parse(line) : { nOk: 0, nErr: -1, raw: s })
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
describe('models file', function() {
|
|
61
|
+
|
|
62
|
+
before(function() {
|
|
63
|
+
fs.mkdirSync(fd, { recursive: true })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
after(function() {
|
|
67
|
+
fs.rmSync(fd, { recursive: true, force: true })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('已具主鍵設定之model經各函數與createStorage後位元組與mtime不變', async function() {
|
|
71
|
+
let fdm = path.join(fd, 'm1')
|
|
72
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
73
|
+
let fn = path.join(fdm, 'users.js')
|
|
74
|
+
fs.writeFileSync(fn, genModel(true), 'utf8')
|
|
75
|
+
let b0 = fs.readFileSync(fn)
|
|
76
|
+
let t0 = fs.statSync(fn).mtimeMs
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 30))
|
|
78
|
+
|
|
79
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm1.sqlite'), db: 'worm', cl: 'users', fdModels: fdm })
|
|
80
|
+
await w.createStorage()
|
|
81
|
+
await w.select()
|
|
82
|
+
await w.selectByPk('k1')
|
|
83
|
+
await w.insert({ id: 'k1', name: 'a' })
|
|
84
|
+
await w.insertBulk({ id: 'k2', name: 'b' })
|
|
85
|
+
await w.save({ id: 'k1', name: 'c' })
|
|
86
|
+
await w.del({ id: 'k2' })
|
|
87
|
+
await w.delAll({ id: 'k1' })
|
|
88
|
+
|
|
89
|
+
assert.ok(fs.readFileSync(fn).equals(b0), 'model檔內容被改寫')
|
|
90
|
+
assert.strictEqual(fs.statSync(fn).mtimeMs, t0, 'model檔被重寫(mtime改變)')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('缺主鍵設定之model亦不寫檔, id於記憶體內視為主鍵', async function() {
|
|
94
|
+
//rulebook R9: 主鍵設定於記憶體內補上, 讀取與寫入函數皆不改寫model檔(含第一次讀取)
|
|
95
|
+
let fdm = path.join(fd, 'm2')
|
|
96
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
97
|
+
let fn = path.join(fdm, 'users.js')
|
|
98
|
+
fs.writeFileSync(fn, genModel(false), 'utf8')
|
|
99
|
+
let b0 = fs.readFileSync(fn)
|
|
100
|
+
let t0 = fs.statSync(fn).mtimeMs
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 30))
|
|
102
|
+
|
|
103
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm2.sqlite'), db: 'worm', cl: 'users', fdModels: fdm })
|
|
104
|
+
await w.select().catch(() => {}) //尚未建表之第一次讀取
|
|
105
|
+
await w.createStorage()
|
|
106
|
+
await w.insert({ id: 'k1', name: 'a' })
|
|
107
|
+
let msg = await w.insert({ id: 'k1', name: 'b' })
|
|
108
|
+
assert.deepStrictEqual(msg, { n: 1, nInserted: 0, ok: 1 }, 'id未被視為主鍵')
|
|
109
|
+
assert.deepStrictEqual(await w.selectByPk('k1'), { id: 'k1', name: 'a', value: null })
|
|
110
|
+
|
|
111
|
+
assert.ok(fs.readFileSync(fn).equals(b0), 'model檔內容被改寫')
|
|
112
|
+
assert.strictEqual(fs.statSync(fn).mtimeMs, t0, 'model檔被重寫(mtime改變)')
|
|
113
|
+
assert.deepStrictEqual(fs.readdirSync(fdm), ['users.js'])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('model之defaultScope帶limit時delAll拒絕且不刪除', async function() {
|
|
117
|
+
//rulebook R1: select依scope之order取前幾筆而sequelize之限量刪除不採order, 兩者對象分歧, 故delAll拒絕
|
|
118
|
+
let fdm = path.join(fd, 'm4')
|
|
119
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
120
|
+
let fn = path.join(fdm, 'users.js')
|
|
121
|
+
fs.writeFileSync(fn, genModel(true).replace('tableName: \'users\'', 'tableName: \'users\', defaultScope: { order: [[\'id\', \'DESC\']], limit: 1 }'), 'utf8')
|
|
122
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm4.sqlite'), db: 'worm', cl: 'users', fdModels: fdm })
|
|
123
|
+
await w.createStorage()
|
|
124
|
+
await w.insert([{ id: 'a' }, { id: 'b' }])
|
|
125
|
+
let r = await w.delAll({}).then(() => null).catch((err) => String(err.message || err))
|
|
126
|
+
assert.ok(r !== null && r.indexOf('defaultScope') >= 0, `未reject: ${r}`)
|
|
127
|
+
let wAll = wo({ url: 'sqlite://:', storage: path.join(fd, 'm4.sqlite'), db: 'worm', cl: 'users', fdModels: path.join(fd, 'm1') })
|
|
128
|
+
assert.deepStrictEqual((await wAll.select()).map((v) => v.id).sort(), ['a', 'b'])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('BIGINT主鍵: 超出安全整數之數值為型別不符, 精確值以標準整數寫法之字串給予', async function() {
|
|
132
|
+
//rulebook R8: 整數類主鍵之數值須為安全整數, 否則已失精度而會命中相鄰主鍵; 超出安全整數之標準整數寫法字串保留為字串(表外細化)
|
|
133
|
+
let fdm = path.join(fd, 'm5')
|
|
134
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
135
|
+
let fn = path.join(fdm, 'tbig.js')
|
|
136
|
+
fs.writeFileSync(fn, [
|
|
137
|
+
'module.exports = function(sequelize, DataTypes) {',
|
|
138
|
+
' return sequelize.define(\'tbig\', {',
|
|
139
|
+
' \'id\': { type: DataTypes.BIGINT, allowNull: false, primaryKey: true },',
|
|
140
|
+
' \'name\': { type: DataTypes.TEXT, allowNull: true },',
|
|
141
|
+
' }, { tableName: \'tbig\' })',
|
|
142
|
+
'}',
|
|
143
|
+
].join('\n'), 'utf8')
|
|
144
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm5.sqlite'), db: 'worm', cl: 'tbig', fdModels: fdm, autoGenPk: false })
|
|
145
|
+
await w.createStorage()
|
|
146
|
+
await w.insert([{ id: '9007199254740992', name: 'even' }, { id: '9007199254740993', name: 'odd' }])
|
|
147
|
+
assert.strictEqual(await w.selectByPk(Number('9007199254740993')), null)
|
|
148
|
+
assert.strictEqual((await w.selectByPk('9007199254740993')).name, 'odd')
|
|
149
|
+
let md = await w.del({ id: Number('9007199254740993') })
|
|
150
|
+
assert.strictEqual(md[0].ok, 0)
|
|
151
|
+
let rd = await w.delAll({ id: Number('9007199254740993') }).then(() => null).catch((err) => String(err.message || err))
|
|
152
|
+
assert.ok(rd !== null && rd.indexOf('does not match column type [int]') >= 0, `delAll未reject: ${rd}`)
|
|
153
|
+
assert.deepStrictEqual((await w.select({ id: { $in: ['9007199254740992', '9007199254740993'] } })).map((v) => v.name).sort(), ['even', 'odd'])
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('model已有其他主鍵時不得把id補成主鍵(不得成為複合主鍵)', async function() {
|
|
157
|
+
//rulebook R9: 僅於model完全沒有主鍵設定時才補id; 否則(sid, id)成為複合主鍵而使主鍵去重失效
|
|
158
|
+
let fdm = path.join(fd, 'm6')
|
|
159
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
160
|
+
fs.writeFileSync(path.join(fdm, 'tsid.js'), [
|
|
161
|
+
'module.exports = function(sequelize, DataTypes) {',
|
|
162
|
+
' return sequelize.define(\'tsid\', {',
|
|
163
|
+
' \'sid\': { type: DataTypes.STRING, allowNull: false, primaryKey: true },',
|
|
164
|
+
' \'id\': { type: DataTypes.STRING, allowNull: true },',
|
|
165
|
+
' }, { tableName: \'tsid\' })',
|
|
166
|
+
'}',
|
|
167
|
+
].join('\n'), 'utf8')
|
|
168
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm6.sqlite'), db: 'worm', cl: 'tsid', fdModels: fdm, pk: 'sid' })
|
|
169
|
+
await w.createStorage()
|
|
170
|
+
let msg = await w.insert([{ sid: 'a', id: 'x' }, { sid: 'a', id: 'y' }])
|
|
171
|
+
assert.deepStrictEqual(msg, { n: 2, nInserted: 1, ok: 1 }, '主鍵去重失效')
|
|
172
|
+
assert.deepStrictEqual(await w.del({ sid: 'a' }), [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('INTEGER主鍵: 收安全整數與標準整數寫法之字串, 其他字串為型別不符', async function() {
|
|
176
|
+
//rulebook R8: 整數類主鍵收安全整數或標準整數寫法之字串(String(Number(s))===s, 轉為數值)
|
|
177
|
+
let fdm = path.join(fd, 'm7')
|
|
178
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
179
|
+
fs.writeFileSync(path.join(fdm, 'tint.js'), [
|
|
180
|
+
'module.exports = function(sequelize, DataTypes) {',
|
|
181
|
+
' return sequelize.define(\'tint\', {',
|
|
182
|
+
' \'id\': { type: DataTypes.INTEGER, allowNull: false, primaryKey: true },',
|
|
183
|
+
' \'name\': { type: DataTypes.TEXT, allowNull: true },',
|
|
184
|
+
' }, { tableName: \'tint\' })',
|
|
185
|
+
'}',
|
|
186
|
+
].join('\n'), 'utf8')
|
|
187
|
+
let w = wo({ url: 'sqlite://:', storage: path.join(fd, 'm7.sqlite'), db: 'worm', cl: 'tint', fdModels: fdm, autoGenPk: false })
|
|
188
|
+
await w.createStorage()
|
|
189
|
+
await w.insert([{ id: 1, name: 'a' }, { id: '2', name: 'b' }])
|
|
190
|
+
assert.strictEqual((await w.selectByPk(1)).name, 'a')
|
|
191
|
+
assert.strictEqual((await w.selectByPk('2')).name, 'b')
|
|
192
|
+
assert.strictEqual(await w.selectByPk('0x1'), null)
|
|
193
|
+
assert.strictEqual(await w.selectByPk(' 1'), null)
|
|
194
|
+
assert.strictEqual((await w.del({ id: 'abc' }))[0].ok, 0)
|
|
195
|
+
let r = await w.save({ id: '0x2', name: 'z' }).then(() => null).catch((err) => String(err.message || err))
|
|
196
|
+
assert.ok(r !== null && r.indexOf('type does not match pk column') >= 0, `save未reject: ${r}`)
|
|
197
|
+
assert.deepStrictEqual((await w.select()).map((v) => v.name).sort(), ['a', 'b'])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('4行程併發操作後model檔不變且全部成功', async function() {
|
|
201
|
+
this.timeout(120000)
|
|
202
|
+
let fdm = path.join(fd, 'm3')
|
|
203
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
204
|
+
let fn = path.join(fdm, 'users.js')
|
|
205
|
+
fs.writeFileSync(fn, genModel(true), 'utf8')
|
|
206
|
+
let b0 = fs.readFileSync(fn)
|
|
207
|
+
let n = 50
|
|
208
|
+
let rs = await Promise.all([0, 1, 2, 3].map((k) => runWorker(fdm, path.join(fd, `m3-${k}.sqlite`), n)))
|
|
209
|
+
for (let r of rs) {
|
|
210
|
+
assert.strictEqual(r.nErr, 0, `子行程失敗: ${JSON.stringify(r)}`)
|
|
211
|
+
assert.strictEqual(r.nOk, n + 1)
|
|
212
|
+
}
|
|
213
|
+
assert.ok(fs.readFileSync(fn).equals(b0), `model檔被改寫, 現為${fs.statSync(fn).size}位元組`)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
})
|