w-orm-reladb 1.1.6 → 1.1.8
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 +206 -131
- 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 +3 -3
- package/src/WOrmReladb.mjs +204 -129
- package/src/cvFind.mjs +789 -0
- package/src/importModels.mjs +180 -114
- package/src/requireModel.js +13 -5
- package/test/api-dist.test.mjs +112 -0
- package/test/api-init.test.mjs +50 -0
- 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/toolg/gDistRollup.mjs +2 -0
- package/src/modifyModel.mjs +0 -113
|
@@ -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
|
+
})
|