neopg 0.0.0 → 1.0.0
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/LICENSE +21 -0
- package/README.cn.md +336 -0
- package/README.md +336 -0
- package/images/neopg-programming.jpeg +0 -0
- package/images/neopg.png +0 -0
- package/lib/ModelChain.js +434 -161
- package/lib/ModelDef.js +4 -1
- package/lib/NeoPG.js +57 -3
- package/lib/SchemaSync.js +77 -36
- package/lib/TransactionScope.js +6 -2
- package/package.json +9 -2
- package/test/test-db.js +194 -21
package/lib/ModelDef.js
CHANGED
|
@@ -10,6 +10,9 @@ class ModelDef {
|
|
|
10
10
|
this.modelName = rawSchema.modelName || rawSchema.tableName
|
|
11
11
|
this.primaryKey = rawSchema.primaryKey || 'id'
|
|
12
12
|
this.columns = rawSchema.column || {}
|
|
13
|
+
this.index = rawSchema.index || []
|
|
14
|
+
this.unique = rawSchema.unique || []
|
|
15
|
+
this.removeIndex = rawSchema.removeIndex || []
|
|
13
16
|
|
|
14
17
|
this._parseColumns()
|
|
15
18
|
|
|
@@ -57,7 +60,7 @@ class ModelDef {
|
|
|
57
60
|
throw new Error(`[NeoPG] Column name '${k}' in table '${this.tableName}' is FORBIDDEN.`)
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
if (
|
|
63
|
+
if (!(/^[a-z][a-z0-9_]*$/i).test(k)) {
|
|
61
64
|
throw new Error(`[NeoPG] Column name '${k}' is invalid. Only alphanumeric and underscore allowed, must start with letter.`)
|
|
62
65
|
}
|
|
63
66
|
}
|
package/lib/NeoPG.js
CHANGED
|
@@ -20,7 +20,7 @@ class NeoPG {
|
|
|
20
20
|
|
|
21
21
|
table(tableName, schema = null) {
|
|
22
22
|
const target = schema || this.defaultSchema
|
|
23
|
-
return new this.ModelChain(this
|
|
23
|
+
return new this.ModelChain(this, {tableName, isRaw: true}, target)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
model(name, schema = null) {
|
|
@@ -28,13 +28,14 @@ class NeoPG {
|
|
|
28
28
|
if (!item) throw new Error(`[NeoPG] Model '${name}' not found.`)
|
|
29
29
|
|
|
30
30
|
const target = schema || this.defaultSchema
|
|
31
|
-
return new item.Class(this
|
|
31
|
+
return new item.Class(this, item.def, target)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// --- 注册 ---
|
|
35
35
|
|
|
36
36
|
add(input) {
|
|
37
|
-
let ModelClass
|
|
37
|
+
let ModelClass
|
|
38
|
+
|
|
38
39
|
if (typeof input === 'function') {
|
|
39
40
|
ModelClass = input
|
|
40
41
|
} else {
|
|
@@ -54,6 +55,10 @@ class NeoPG {
|
|
|
54
55
|
return this
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
define(model) {
|
|
59
|
+
return this.add(model)
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
// --- 事务 ---
|
|
58
63
|
async transaction(callback) {
|
|
59
64
|
return await this.driver.begin(async (trxSql) => {
|
|
@@ -62,13 +67,62 @@ class NeoPG {
|
|
|
62
67
|
})
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
begin(callback) {
|
|
71
|
+
return this.transaction(callback)
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
// --- 同步 ---
|
|
66
75
|
async sync(options = {}) {
|
|
76
|
+
if (!options || typeof options !== 'object') {
|
|
77
|
+
options = {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!options.schema) options.schema = this.defaultSchema
|
|
81
|
+
|
|
67
82
|
for (const item of this.registry.values()) {
|
|
68
83
|
await SchemaSync.execute(this.driver, item.def, this, options)
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
/**
|
|
88
|
+
* 监听 Postgres 消息通道
|
|
89
|
+
* @param {string} channel - 通道名称
|
|
90
|
+
* @param {Function} callback - (payload) => {}
|
|
91
|
+
* @returns {Object} 包含 unlisten 方法的对象
|
|
92
|
+
*/
|
|
93
|
+
async listen(channel, callback) {
|
|
94
|
+
// postgres.js 的 listen 返回一个 Promise<void>
|
|
95
|
+
// 但它内部会维持连接。我们需要提供一种方式来取消监听。
|
|
96
|
+
// postgres.js v3 使用 sql.listen(channel, cb) 并返回一个 state 对象用于 close
|
|
97
|
+
const listener = await this.sql.listen(channel, (payload) => {
|
|
98
|
+
// 可以在这里统一处理 JSON 解析等
|
|
99
|
+
try {
|
|
100
|
+
const data = JSON.parse(payload)
|
|
101
|
+
callback(data)
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// 无法解析则返回原始字符串
|
|
104
|
+
callback(payload)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
// 返回一个句柄用于取消监听
|
|
110
|
+
close: () => listener.unlisten()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 发送通知
|
|
116
|
+
* @param {string} channel
|
|
117
|
+
* @param {string|Object} payload
|
|
118
|
+
*/
|
|
119
|
+
async notify(channel, payload) {
|
|
120
|
+
const data = typeof payload === 'object' ? JSON.stringify(payload) : payload
|
|
121
|
+
|
|
122
|
+
// 使用 sql.notify 是最高效的
|
|
123
|
+
await this.sql.notify(channel, data)
|
|
124
|
+
}
|
|
125
|
+
|
|
72
126
|
async close() {
|
|
73
127
|
await this.driver.end()
|
|
74
128
|
}
|
package/lib/SchemaSync.js
CHANGED
|
@@ -32,17 +32,33 @@ const DefaultWithType = ['varchar', 'char', 'text', 'bytea', 'timestamp', 'times
|
|
|
32
32
|
|
|
33
33
|
class SchemaSync {
|
|
34
34
|
|
|
35
|
+
static async createSchema(sql, schema) {
|
|
36
|
+
return sql.unsafe(`create schema if not exists ${schema};`)
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
/**
|
|
36
40
|
* 入口方法
|
|
37
41
|
*/
|
|
38
42
|
static async execute(sql, def, ctx, options = {}) {
|
|
39
43
|
const debug = options.debug || false;
|
|
40
44
|
const force = options.force || false;
|
|
41
|
-
const dropNotExistCol = options.dropNotExistCol || false;
|
|
42
|
-
const schema = ctx.defaultSchema || 'public';
|
|
45
|
+
const dropNotExistCol = force || options.dropNotExistCol || false;
|
|
46
|
+
const schema = options.schema || ctx.defaultSchema || 'public';
|
|
43
47
|
const tableName = def.tableName;
|
|
44
48
|
const curTableName = `${schema}.${tableName}`;
|
|
45
49
|
|
|
50
|
+
// [递归锁初始化] 记录本次同步过程中已经处理过的 Model,防止死循环
|
|
51
|
+
if (!options.syncedModels) {
|
|
52
|
+
options.syncedModels = new Set();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 如果该表在本次递归链中已经同步过,则跳过
|
|
56
|
+
if (options.syncedModels.has(def.modelName)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// 标记当前表已开始同步
|
|
60
|
+
options.syncedModels.add(def.modelName);
|
|
61
|
+
|
|
46
62
|
if (debug) console.log(`检测数据表 ${tableName} 的column...`);
|
|
47
63
|
|
|
48
64
|
// 0. 检查列定义的合法性 (移植自 _checkFixColumn)
|
|
@@ -72,7 +88,7 @@ class SchemaSync {
|
|
|
72
88
|
// 同步索引、约束、外键
|
|
73
89
|
await this.syncIndex(sql, def, schema, curTableName, debug);
|
|
74
90
|
await this.syncUnique(sql, def, schema, curTableName, debug);
|
|
75
|
-
await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx);
|
|
91
|
+
await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
|
|
76
92
|
return;
|
|
77
93
|
}
|
|
78
94
|
|
|
@@ -80,7 +96,7 @@ class SchemaSync {
|
|
|
80
96
|
// 获取现有列信息
|
|
81
97
|
const cols = await sql`
|
|
82
98
|
SELECT column_name, data_type, column_default, character_maximum_length,
|
|
83
|
-
numeric_precision, numeric_scale, is_nullable
|
|
99
|
+
numeric_precision, numeric_scale, is_nullable, is_generated
|
|
84
100
|
FROM information_schema.columns
|
|
85
101
|
WHERE table_name = ${tableName}
|
|
86
102
|
AND table_schema = ${schema}
|
|
@@ -99,7 +115,7 @@ class SchemaSync {
|
|
|
99
115
|
await this.syncIndex(sql, def, schema, curTableName, debug);
|
|
100
116
|
await this.syncUnique(sql, def, schema, curTableName, debug);
|
|
101
117
|
await this.autoRemoveIndex(sql, def, schema, tableName, debug);
|
|
102
|
-
await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx);
|
|
118
|
+
await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
|
|
103
119
|
|
|
104
120
|
if (debug) console.log(` - 表结构同步完成 (${tableName}) - `);
|
|
105
121
|
}
|
|
@@ -130,7 +146,7 @@ class SchemaSync {
|
|
|
130
146
|
|
|
131
147
|
if (col.default !== undefined) {
|
|
132
148
|
if (col.default === null) line += ' default null';
|
|
133
|
-
else line += ` default
|
|
149
|
+
else line += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
|
|
134
150
|
}
|
|
135
151
|
}
|
|
136
152
|
colSqls.push(line);
|
|
@@ -190,7 +206,7 @@ class SchemaSync {
|
|
|
190
206
|
|
|
191
207
|
if (col.default !== undefined) {
|
|
192
208
|
if (col.default === null) addSql += ' default null';
|
|
193
|
-
else addSql += ` default
|
|
209
|
+
else addSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
|
|
194
210
|
}
|
|
195
211
|
|
|
196
212
|
if (debug) console.log(addSql);
|
|
@@ -215,7 +231,7 @@ class SchemaSync {
|
|
|
215
231
|
await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
|
|
216
232
|
let reAddSql = `ALTER TABLE ${curTableName} ADD COLUMN ${this.fmtColName(k)} ${col.type}`;
|
|
217
233
|
if (col.notNull !== false) reAddSql += ' not null';
|
|
218
|
-
if (col.default !== undefined) reAddSql += ` default
|
|
234
|
+
if (col.default !== undefined) reAddSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
|
|
219
235
|
await sql.unsafe(reAddSql);
|
|
220
236
|
col.changed = true; // 标记变更,供外键处理使用
|
|
221
237
|
continue;
|
|
@@ -240,7 +256,7 @@ class SchemaSync {
|
|
|
240
256
|
// 简单比对逻辑 (注:PG存储的默认值格式可能不同,这里仅作简单触发)
|
|
241
257
|
// 实际生产中可能需要更复杂的解析,这里保留原逻辑结构
|
|
242
258
|
// 原逻辑用了 _realDefault 方法,这里我们简单处理,仅当需要时设置
|
|
243
|
-
let default_val_sql = col.default === null ? 'null' :
|
|
259
|
+
let default_val_sql = col.default === null ? 'null' : `$_${qtag}_$${col.default}$_${qtag}_$`;
|
|
244
260
|
// 这里为了简化,每次都重设默认值(开销很小),或者你需要实现 _realDefault
|
|
245
261
|
await sql.unsafe(`ALTER TABLE ${curTableName} ALTER COLUMN ${this.fmtColName(k)} SET DEFAULT ${default_val_sql}`);
|
|
246
262
|
}
|
|
@@ -259,24 +275,43 @@ class SchemaSync {
|
|
|
259
275
|
|
|
260
276
|
// 7. Drop Not Exist (Force Mode)
|
|
261
277
|
if (dropNotExistCol) {
|
|
262
|
-
for (let
|
|
263
|
-
|
|
264
|
-
|
|
278
|
+
for (let dbColName in inf) {
|
|
279
|
+
// 如果代码中没定义,且不是刚改名的
|
|
280
|
+
if (!def.columns[dbColName] && !renameTable[dbColName]) {
|
|
281
|
+
|
|
282
|
+
const dbCol = inf[dbColName];
|
|
283
|
+
|
|
284
|
+
// [核心逻辑] 如果是数据库层面的虚拟列 (is_generated == 'ALWAYS'),豁免,不删除
|
|
285
|
+
if (dbCol.is_generated === 'ALWAYS') {
|
|
286
|
+
if (debug) console.log(`[NeoPG] Ignoring DB-only generated column: ${dbColName}`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// [额外建议] 如果是 identity 列 (自增列的一种新形式),通常也建议豁免,防止误删
|
|
291
|
+
// if (dbCol.is_identity === 'YES') continue;
|
|
292
|
+
|
|
293
|
+
if (debug) console.log(`Deleting unused column: ${dbColName}`);
|
|
294
|
+
await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(dbColName)}`);
|
|
265
295
|
}
|
|
266
296
|
}
|
|
267
297
|
}
|
|
298
|
+
/* for (let k in inf) {
|
|
299
|
+
if (!def.columns[k] && !renameTable[k]) {
|
|
300
|
+
await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
|
|
301
|
+
}
|
|
302
|
+
} */
|
|
268
303
|
}
|
|
269
304
|
|
|
270
305
|
// --- 索引同步 ---
|
|
271
306
|
static async syncIndex(sql, def, schema, curTableName, debug) {
|
|
272
307
|
// 假设索引定义在 def.rawSchema.index (数组)
|
|
273
308
|
// ModelDef 需要暴露这个属性,或 def.indices
|
|
274
|
-
const indices = def.
|
|
309
|
+
const indices = def.index || [];
|
|
275
310
|
if (!Array.isArray(indices)) return;
|
|
276
311
|
|
|
277
312
|
for (const indname of indices) {
|
|
278
313
|
// 检查 removeIndex 配置
|
|
279
|
-
const removeIndex = def.
|
|
314
|
+
const removeIndex = def.removeIndex || [];
|
|
280
315
|
if (removeIndex.includes(indname)) continue;
|
|
281
316
|
|
|
282
317
|
// 检查列是否存在
|
|
@@ -310,7 +345,7 @@ class SchemaSync {
|
|
|
310
345
|
}
|
|
311
346
|
|
|
312
347
|
static async syncUnique(sql, def, schema, curTableName, debug) {
|
|
313
|
-
const uniques = def.
|
|
348
|
+
const uniques = def.unique || [];
|
|
314
349
|
if (!Array.isArray(uniques)) return;
|
|
315
350
|
|
|
316
351
|
for (const indname of uniques) {
|
|
@@ -347,8 +382,8 @@ class SchemaSync {
|
|
|
347
382
|
const currentIdxNames = allIdx.map(i => i.indexname);
|
|
348
383
|
|
|
349
384
|
// 2. 计算应该保留的索引名
|
|
350
|
-
const indices = def.
|
|
351
|
-
const uniques = def.
|
|
385
|
+
const indices = def.index || [];
|
|
386
|
+
const uniques = def.unique || [];
|
|
352
387
|
|
|
353
388
|
const keepSet = new Set();
|
|
354
389
|
const makeName = (n) => `${tableName}_${n.split(',').map(x=>x.trim()).filter(x=>x).join('_')}_idx`;
|
|
@@ -366,7 +401,7 @@ class SchemaSync {
|
|
|
366
401
|
}
|
|
367
402
|
|
|
368
403
|
// --- 外键同步 ---
|
|
369
|
-
static async syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx) {
|
|
404
|
+
static async syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options) {
|
|
370
405
|
// 1. 收集定义中的外键
|
|
371
406
|
// 格式: { fkName: "xxx", createSql: "..." }
|
|
372
407
|
let targetFKs = new Map();
|
|
@@ -378,27 +413,32 @@ class SchemaSync {
|
|
|
378
413
|
|
|
379
414
|
// 解析 ref: "ModelName:colName"
|
|
380
415
|
const [refModelName, refColName] = this._parseRef(col.ref, k);
|
|
416
|
+
|
|
417
|
+
// 查找被引用的 Model 定义
|
|
418
|
+
// 假设 ctx.registry.get 返回 { Class, def }
|
|
419
|
+
const targetModelItem = ctx.registry.get(refModelName);
|
|
381
420
|
|
|
382
|
-
// 加载目标模型 (这里需要通过 ctx (NeoPG实例) 获取其他模型)
|
|
383
|
-
// 假设 ctx.registry.get(refModelName) 能拿到
|
|
384
|
-
// 或者如果是文件路径,尝试 require
|
|
385
|
-
|
|
386
|
-
// 为了简化复刻,这里假设我们能拿到目标表名
|
|
387
|
-
// 在原逻辑中是 require 文件,这里建议 NeoPG 注册机制解决
|
|
388
|
-
// 这里做一个适配:
|
|
389
421
|
let targetTableName = '';
|
|
390
|
-
|
|
391
|
-
|
|
422
|
+
|
|
423
|
+
if (targetModelItem) {
|
|
424
|
+
targetTableName = targetModelItem.def.tableName;
|
|
425
|
+
// --- [关键] 递归同步 ---
|
|
426
|
+
// 在创建外键之前,确保目标表已经存在且结构最新
|
|
427
|
+
if (debug) console.log(`[Recursive Sync] Triggered by FK: ${def.modelName} -> ${refModelName}`);
|
|
428
|
+
|
|
429
|
+
await this.execute(sql, targetModelItem.def, ctx, options);
|
|
392
430
|
} else {
|
|
393
|
-
|
|
394
|
-
|
|
431
|
+
// 目标模型未注册,可能是直接引用的表名 (Fallback)
|
|
432
|
+
if (debug) console.warn(`[NeoPG] Referenced model '${refModelName}' not found in registry. Using as table name.`);
|
|
433
|
+
targetTableName = refModelName.toLowerCase();
|
|
395
434
|
}
|
|
396
435
|
|
|
397
|
-
//
|
|
436
|
+
// 准备外键 SQL
|
|
398
437
|
const fkName = `${def.tableName}_${k}_fkey`;
|
|
399
438
|
|
|
400
439
|
// 构建 REFERENCES 子句
|
|
401
440
|
let refSql = `REFERENCES ${schema}.${targetTableName} (${refColName})`;
|
|
441
|
+
|
|
402
442
|
if (col.refActionUpdate) refSql += ` ON UPDATE ${col.refActionUpdate}`;
|
|
403
443
|
else refSql += ` ON UPDATE CASCADE`; // 默认
|
|
404
444
|
|
|
@@ -493,14 +533,15 @@ class SchemaSync {
|
|
|
493
533
|
|
|
494
534
|
static _parseRef(refstr, curColumn) {
|
|
495
535
|
if (refstr.includes(':')) {
|
|
496
|
-
const parts = refstr.split(':')
|
|
536
|
+
const parts = refstr.split(':')
|
|
497
537
|
// 处理 Model:col 格式,取最后一部分做 col,前面做 Model
|
|
498
|
-
const col = parts.pop()
|
|
499
|
-
const model = parts.join(':')
|
|
500
|
-
return [model, col]
|
|
538
|
+
const col = parts.pop()
|
|
539
|
+
const model = parts.join(':')
|
|
540
|
+
return [model, col]
|
|
501
541
|
}
|
|
502
|
-
|
|
542
|
+
|
|
543
|
+
return [refstr, curColumn]
|
|
503
544
|
}
|
|
504
545
|
}
|
|
505
546
|
|
|
506
|
-
module.exports = SchemaSync
|
|
547
|
+
module.exports = SchemaSync
|
package/lib/TransactionScope.js
CHANGED
|
@@ -9,14 +9,14 @@ class TransactionScope {
|
|
|
9
9
|
|
|
10
10
|
table(tableName, schema = null) {
|
|
11
11
|
const target = schema || this.parent.defaultSchema
|
|
12
|
-
return new this.parent.ModelChain(this
|
|
12
|
+
return new this.parent.ModelChain(this, {tableName, isRaw: true}, target)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
model(name, schema = null) {
|
|
16
16
|
const item = this.parent.registry.get(name)
|
|
17
17
|
if (!item) throw new Error(`[NeoPG] Model '${name}' not found.`)
|
|
18
18
|
const target = schema || this.parent.defaultSchema
|
|
19
|
-
return new item.Class(this
|
|
19
|
+
return new item.Class(this, item.def, target)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async transaction(callback) {
|
|
@@ -24,6 +24,10 @@ class TransactionScope {
|
|
|
24
24
|
return await callback(new TransactionScope(this.parent, sp))
|
|
25
25
|
})
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
begin(callback) {
|
|
29
|
+
return this.transaction(callback)
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
module.exports = TransactionScope
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neopg",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "orm for postgres",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"neopg",
|
|
7
|
+
"NeoPG",
|
|
8
|
+
"pg",
|
|
9
|
+
"neo",
|
|
10
|
+
"pgorm",
|
|
11
|
+
"psqlorm",
|
|
6
12
|
"postgres",
|
|
7
13
|
"orm",
|
|
8
|
-
"database"
|
|
14
|
+
"database",
|
|
15
|
+
"postgres.js"
|
|
9
16
|
],
|
|
10
17
|
"homepage": "https://github.com/master-genius/neopg#readme",
|
|
11
18
|
"bugs": {
|
package/test/test-db.js
CHANGED
|
@@ -1,44 +1,217 @@
|
|
|
1
1
|
// index.js
|
|
2
2
|
const NeoPG = require('../lib/NeoPG.js')
|
|
3
|
-
const ModelChain = NeoPG
|
|
3
|
+
const {dataTypes, ModelChain} = NeoPG
|
|
4
4
|
|
|
5
5
|
// 1. 初始化
|
|
6
6
|
const db = new NeoPG({
|
|
7
7
|
host: '127.0.0.1',
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
user: 'wy',
|
|
9
|
+
database: 'wdata',
|
|
10
|
+
schema: 'test'
|
|
10
11
|
});
|
|
11
12
|
|
|
12
13
|
// 2. 定义模型
|
|
13
|
-
const
|
|
14
|
+
const User = {
|
|
14
15
|
modelName: 'User',
|
|
15
|
-
tableName: '
|
|
16
|
+
tableName: 'users',
|
|
17
|
+
primaryKey: 'id',
|
|
18
|
+
|
|
16
19
|
column: {
|
|
17
|
-
id: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
id : {
|
|
21
|
+
type: dataTypes.UID
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @type {column}
|
|
26
|
+
*/
|
|
27
|
+
username: {
|
|
28
|
+
type: dataTypes.STRING(50)
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
mobile: {
|
|
32
|
+
type: dataTypes.STRING(16)
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
mobile_state: {
|
|
36
|
+
type: dataTypes.SMALLINT,
|
|
37
|
+
default: 0
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
//真实姓名
|
|
41
|
+
realname: {
|
|
42
|
+
type: dataTypes.STRING(50),
|
|
43
|
+
default: ''
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
sex: {
|
|
47
|
+
type: dataTypes.SMALLINT,
|
|
48
|
+
default: 0
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
//其他信息,以JSON格式存储
|
|
52
|
+
info: {
|
|
53
|
+
type: dataTypes.TEXT,
|
|
54
|
+
default: ''
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @type {column}
|
|
59
|
+
*/
|
|
60
|
+
passwd: {
|
|
61
|
+
type: dataTypes.STRING(240)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
//当验证失败,需要进行重复密码验证
|
|
65
|
+
repasswd: {
|
|
66
|
+
type: dataTypes.STRING(240)
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @type {column}
|
|
71
|
+
*/
|
|
72
|
+
level: {
|
|
73
|
+
type: 'smallint',
|
|
74
|
+
default: 1,
|
|
75
|
+
validate: v => {
|
|
76
|
+
return v >= 0 && v < 99
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @type {column}
|
|
82
|
+
*/
|
|
83
|
+
email: {
|
|
84
|
+
type: dataTypes.STRING(60),
|
|
85
|
+
default: ''
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
email_state: {
|
|
89
|
+
type: dataTypes.SMALLINT,
|
|
90
|
+
default: 0
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
create_time: {
|
|
94
|
+
type: dataTypes.BIGINT,
|
|
95
|
+
default: 0,
|
|
96
|
+
timestamp: 'insert'
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
update_time: {
|
|
100
|
+
type: dataTypes.BIGINT,
|
|
101
|
+
default: 0,
|
|
102
|
+
timestamp: 'update'
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
failed_count: {
|
|
106
|
+
type: dataTypes.INT,
|
|
107
|
+
default: 0
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
failed_time: {
|
|
111
|
+
type: dataTypes.BIGINT,
|
|
112
|
+
default: 0
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
forbid: {
|
|
116
|
+
type: dataTypes.SMALLINT,
|
|
117
|
+
default: 0
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
is_root: {
|
|
121
|
+
type: dataTypes.SMALLINT,
|
|
122
|
+
default: 0
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
//索引
|
|
127
|
+
index: [
|
|
128
|
+
'create_time',
|
|
129
|
+
'level',
|
|
130
|
+
'is_root'
|
|
131
|
+
],
|
|
132
|
+
|
|
133
|
+
//唯一索引
|
|
134
|
+
unique: [
|
|
135
|
+
'username',
|
|
136
|
+
'email'
|
|
137
|
+
]
|
|
138
|
+
}
|
|
22
139
|
|
|
23
140
|
// 3. 注册
|
|
24
|
-
db.add(
|
|
141
|
+
db.add(User);
|
|
25
142
|
|
|
26
143
|
;(async () => {
|
|
144
|
+
await db.sync({force: true, debug: true})
|
|
27
145
|
// 插入
|
|
28
|
-
await db.model('User').insert({ name: 'Neo' });
|
|
29
146
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
147
|
+
await db.model('User').where('1=1').delete()
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
console.log(
|
|
151
|
+
await db.model('User')
|
|
152
|
+
.returning(['id', 'username', 'level', 'create_time'])
|
|
153
|
+
.insert([
|
|
154
|
+
{
|
|
155
|
+
username: 'Neo',
|
|
156
|
+
email: '123@w.com',
|
|
157
|
+
level: Math.floor((Math.random() * 105))
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
username: 'PG',
|
|
161
|
+
email: '1234@w.com',
|
|
162
|
+
level: Math.floor((Math.random() * 100))
|
|
163
|
+
}
|
|
164
|
+
])
|
|
165
|
+
)
|
|
166
|
+
} catch(err) {
|
|
167
|
+
console.error('\x1b[7;5m随机测试:让level超过99,validate验证失败\x1b[0m')
|
|
168
|
+
}
|
|
35
169
|
|
|
36
170
|
// 事务
|
|
37
171
|
await db.transaction(async tx => {
|
|
38
|
-
|
|
172
|
+
let data = {
|
|
173
|
+
level: Math.floor(Math.random() * 100),
|
|
174
|
+
info: `age=${Math.floor(Math.random() * 10 + 20)};delete from users;`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('update', data)
|
|
178
|
+
|
|
179
|
+
let result = await tx.model('User').where(tx.sql`level > 10`).returning('*').update(data)
|
|
180
|
+
console.log(result)
|
|
181
|
+
|
|
182
|
+
console.log(
|
|
183
|
+
'test avg',
|
|
184
|
+
await tx.model('User').avg('level')
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
console.log(
|
|
188
|
+
'test max',
|
|
189
|
+
await tx.model('User').max('level')
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
console.log(
|
|
193
|
+
'test min',
|
|
194
|
+
await tx.model('User').min('username')
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
let n = Math.floor(Math.random() * 10)
|
|
198
|
+
|
|
199
|
+
if (n > 7) {
|
|
200
|
+
console.error('\x1b[7;5m随机测试:将会让事物执行失败\x1b[0m')
|
|
201
|
+
await tx.model('User').insert({username: 'Neo'})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log('test count',
|
|
205
|
+
await tx.model('User').where('level', '<', 10).count()
|
|
206
|
+
)
|
|
207
|
+
|
|
39
208
|
// 嵌套
|
|
40
|
-
await tx.transaction(async subTx => {
|
|
209
|
+
/* await tx.transaction(async subTx => {
|
|
41
210
|
await subTx.table('logs').insert({ msg: 'log' });
|
|
42
|
-
});
|
|
43
|
-
})
|
|
211
|
+
}); */
|
|
212
|
+
}).catch(err => {
|
|
213
|
+
console.error(err)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
db.close()
|
|
44
217
|
})();
|