neopg 2.0.6 → 2.0.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/lib/ModelChain.js CHANGED
@@ -57,8 +57,8 @@ class ModelChain {
57
57
 
58
58
  _destroy() {
59
59
  this._executed = true
60
- this.def = null
61
- this.ctx = null
60
+ //this.def = null
61
+ //this.ctx = null
62
62
  // this.sql = null // 可选:保留引用以便 debug,或者释放
63
63
  }
64
64
 
package/lib/NeoPG.js CHANGED
@@ -25,7 +25,6 @@ function toPascalCase(str) {
25
25
  class NeoPG {
26
26
  constructor(config) {
27
27
  this.driver = postgres(config)
28
- this.ModelChain = ModelChain
29
28
  this.sql = this.driver
30
29
 
31
30
  this.defaultSchema = config.schema || 'public'
@@ -35,7 +34,7 @@ class NeoPG {
35
34
 
36
35
  table(tableName, schema = null) {
37
36
  const target = schema || this.defaultSchema
38
- let m = new this.ModelChain(this, {tableName, isRaw: true}, target)
37
+ let m = new ModelChain(this, {tableName, isRaw: true}, target)
39
38
  m._isRaw = true
40
39
  return m
41
40
  }
@@ -65,7 +64,7 @@ class NeoPG {
65
64
  if (typeof input === 'function') {
66
65
  ModelClass = input
67
66
  } else {
68
- ModelClass = this.ModelChain.from(input)
67
+ ModelClass = ModelChain.from(input)
69
68
  }
70
69
 
71
70
  const rawSchema = ModelClass.schema
package/lib/SchemaSync.js CHANGED
@@ -28,7 +28,7 @@ const DataTypeMap = {
28
28
  const Numerics = ['smallint','bigint','integer','decimal','numeric', 'int'];
29
29
  const Strings = ['char', 'varchar', 'text'];
30
30
  const TypeWithBrackets = ['character varying', 'character', 'decimal', 'numeric'];
31
- const DefaultWithType = ['varchar', 'char', 'text', 'bytea', 'timestamp', 'timestampz', 'date', 'time'];
31
+ const DefaultWithType = ['varchar', 'char', 'text', 'bytea', 'timestamp', 'timestamptz', 'date', 'time'];
32
32
 
33
33
  class SchemaSync {
34
34
 
@@ -47,29 +47,26 @@ class SchemaSync {
47
47
  const tableName = def.tableName;
48
48
  const curTableName = `${schema}.${tableName}`;
49
49
 
50
- // [递归锁初始化] 记录本次同步过程中已经处理过的 Model,防止死循环
50
+ // [递归锁初始化]
51
51
  if (!options.syncedModels) {
52
52
  options.syncedModels = new Set();
53
53
  }
54
54
 
55
- // 如果该表在本次递归链中已经同步过,则跳过
56
55
  if (options.syncedModels.has(def.modelName)) {
57
56
  return;
58
57
  }
59
- // 标记当前表已开始同步
60
58
  options.syncedModels.add(def.modelName);
61
59
 
62
60
  if (debug) console.log(`检测数据表 ${tableName} 的column...`);
63
61
 
64
- // 0. 检查列定义的合法性 (移植自 _checkFixColumn)
65
- // 简单复刻:NeoPG 的 ModelDef 已经做了一部分,这里补充检查
62
+ // 0. 检查列定义的合法性
66
63
  for (let k in def.columns) {
67
64
  if (k.toLowerCase() !== k) {
68
65
  console.warn(`[NeoPG Warning] ${tableName} column ${k} 包含大写,Postgres会转小写,建议代码中改为小写。`);
69
66
  }
70
67
  }
71
68
 
72
- // 1. 获取 Schema OID (用于外键查询)
69
+ // 1. 获取 Schema OID
73
70
  let schemaOid = null;
74
71
  const nsRes = await sql`SELECT oid FROM pg_namespace WHERE nspname = ${schema}`;
75
72
  if (nsRes.length > 0) schemaOid = nsRes[0].oid;
@@ -85,7 +82,6 @@ class SchemaSync {
85
82
  // A. 表不存在 -> 创建
86
83
  if (tableInfo.length === 0) {
87
84
  await this.createTable(sql, def, schema, curTableName, debug);
88
- // 同步索引、约束、外键
89
85
  await this.syncIndex(sql, def, schema, curTableName, debug);
90
86
  await this.syncUnique(sql, def, schema, curTableName, debug);
91
87
  await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
@@ -93,7 +89,6 @@ class SchemaSync {
93
89
  }
94
90
 
95
91
  // B. 表存在 -> 增量同步
96
- // 获取现有列信息
97
92
  const cols = await sql`
98
93
  SELECT column_name, data_type, column_default, character_maximum_length,
99
94
  numeric_precision, numeric_scale, is_nullable, is_generated
@@ -106,11 +101,6 @@ class SchemaSync {
106
101
  const inf = {};
107
102
  for (const c of cols) inf[c.column_name] = c;
108
103
 
109
- // 预处理 rename 逻辑需要
110
- // 若存在 dropIndex 但是不存在 removeIndex 则指向 dropIndex (原逻辑)
111
- // NeoPG 中这部分配置可能需要从 def 传递进来,假设 def.rawSchema 包含这些配置
112
- // 为了简化,我们假设 def 上挂载了 extra 属性用于存储 index/unique 等非 column 配置
113
-
114
104
  await this.syncColumn(sql, def, inf, curTableName, debug, force, dropNotExistCol);
115
105
  await this.syncIndex(sql, def, schema, curTableName, debug);
116
106
  await this.syncUnique(sql, def, schema, curTableName, debug);
@@ -120,6 +110,22 @@ class SchemaSync {
120
110
  if (debug) console.log(` - 表结构同步完成 (${tableName}) - `);
121
111
  }
122
112
 
113
+ // --- [NEW] 抽取出的默认值推导逻辑 ---
114
+ static _ensureDefaultValue(col) {
115
+ // 如果已经有默认值定义,则跳过
116
+ if (col.default !== undefined) return;
117
+
118
+ const pt = this._parseType(col.type);
119
+
120
+ if (col.type.includes('[')) {
121
+ col.default = '{}';
122
+ } else if (Numerics.includes(pt)) {
123
+ col.default = 0;
124
+ } else if (Strings.includes(pt)) {
125
+ col.default = '';
126
+ }
127
+ }
128
+
123
129
  // --- 创建表逻辑 ---
124
130
  static async createTable(sql, def, schema, curTableName, debug) {
125
131
  let colSqls = [];
@@ -136,13 +142,8 @@ class SchemaSync {
136
142
  } else {
137
143
  if (col.notNull !== false) line += ' not null';
138
144
 
139
- // 自动检测默认值逻辑
140
- let pt = this._parseType(col.type);
141
- if (col.default === undefined) {
142
- if (col.type.includes('[')) col.default = '{}';
143
- else if (Numerics.includes(pt)) col.default = 0;
144
- else if (Strings.includes(pt)) col.default = '';
145
- }
145
+ // [Modified] 使用统一方法推导默认值
146
+ this._ensureDefaultValue(col);
146
147
 
147
148
  if (col.default !== undefined) {
148
149
  if (col.default === null) line += ' default null';
@@ -152,7 +153,6 @@ class SchemaSync {
152
153
  colSqls.push(line);
153
154
  }
154
155
 
155
- // 联合主键
156
156
  if (Array.isArray(def.primaryKey)) {
157
157
  colSqls.push(`primary key (${def.primaryKey.join(',')})`);
158
158
  }
@@ -162,7 +162,7 @@ class SchemaSync {
162
162
  await sql.unsafe(createSql);
163
163
  }
164
164
 
165
- // --- 列同步逻辑 (核心复杂逻辑) ---
165
+ // --- 列同步逻辑 ---
166
166
  static async syncColumn(sql, def, inf, curTableName, debug, force, dropNotExistCol) {
167
167
  const qtag = randstring(12);
168
168
  let renameTable = {};
@@ -184,7 +184,7 @@ class SchemaSync {
184
184
  const oldName = col.oldName.trim();
185
185
  if (inf[k] === undefined && inf[oldName]) {
186
186
  await sql.unsafe(`ALTER TABLE ${curTableName} RENAME ${this.fmtColName(oldName)} TO ${this.fmtColName(k)}`);
187
- inf[k] = inf[oldName]; // 更新内存信息
187
+ inf[k] = inf[oldName];
188
188
  renameTable[oldName] = true;
189
189
  }
190
190
  }
@@ -197,12 +197,8 @@ class SchemaSync {
197
197
  let addSql = `ALTER TABLE ${curTableName} ADD COLUMN ${this.fmtColName(k)} ${col.type}`;
198
198
  if (col.notNull !== false) addSql += ' not null';
199
199
 
200
- // 默认值补全
201
- if (col.default === undefined) {
202
- if (col.type.includes('[')) col.default = '{}';
203
- else if (Numerics.includes(pt)) col.default = 0;
204
- else if (Strings.includes(pt)) col.default = '';
205
- }
200
+ // [Modified] 使用统一方法推导默认值
201
+ this._ensureDefaultValue(col);
206
202
 
207
203
  if (col.default !== undefined) {
208
204
  if (col.default === null) addSql += ' default null';
@@ -215,13 +211,12 @@ class SchemaSync {
215
211
  }
216
212
 
217
213
  if (col.typeLock) continue;
218
- if (real_type === null) continue; // 未知类型跳过
214
+ if (real_type === null) continue;
219
215
 
220
216
  // 4. Check Type Change
221
217
  if (this._compareType(inf[k], col, real_type) === false) {
222
218
  let alterSql = `ALTER TABLE ${curTableName} ALTER COLUMN ${this.fmtColName(k)} TYPE ${col.type}`;
223
219
 
224
- // 特殊处理字符串转非字符串的问题
225
220
  const isDbString = inf[k].data_type === 'text' || inf[k].data_type.includes('character');
226
221
  const isTargetString = Strings.includes(this._parseType(col.type));
227
222
 
@@ -229,11 +224,20 @@ class SchemaSync {
229
224
  if (col.force) {
230
225
  // 强制重建列
231
226
  await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
227
+
232
228
  let reAddSql = `ALTER TABLE ${curTableName} ADD COLUMN ${this.fmtColName(k)} ${col.type}`;
233
229
  if (col.notNull !== false) reAddSql += ' not null';
234
- if (col.default !== undefined) reAddSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
230
+
231
+ // [Modified] 强制重建时,必须先推导默认值,否则 not null 会导致已有数据报错
232
+ this._ensureDefaultValue(col);
233
+
234
+ if (col.default !== undefined) {
235
+ if (col.default === null) reAddSql += ' default null';
236
+ else reAddSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
237
+ }
238
+
235
239
  await sql.unsafe(reAddSql);
236
- col.changed = true; // 标记变更,供外键处理使用
240
+ col.changed = true;
237
241
  continue;
238
242
  } else {
239
243
  console.error(`Error: ${k} 从字符串转向其他类型无转换规则,且未设置force选项。`);
@@ -251,13 +255,9 @@ class SchemaSync {
251
255
  }
252
256
  }
253
257
 
254
- // 5. Check Default Value
258
+ // 5. Check Default Value (Only alter if explicitly changed or added)
255
259
  if (col.default !== undefined) {
256
- // 简单比对逻辑 (注:PG存储的默认值格式可能不同,这里仅作简单触发)
257
- // 实际生产中可能需要更复杂的解析,这里保留原逻辑结构
258
- // 原逻辑用了 _realDefault 方法,这里我们简单处理,仅当需要时设置
259
260
  let default_val_sql = col.default === null ? 'null' : `$_${qtag}_$${col.default}$_${qtag}_$`;
260
- // 这里为了简化,每次都重设默认值(开销很小),或者你需要实现 _realDefault
261
261
  await sql.unsafe(`ALTER TABLE ${curTableName} ALTER COLUMN ${this.fmtColName(k)} SET DEFAULT ${default_val_sql}`);
262
262
  }
263
263
 
@@ -266,67 +266,43 @@ class SchemaSync {
266
266
  if (inf[k].is_nullable === 'YES') {
267
267
  await sql.unsafe(`ALTER TABLE ${curTableName} ALTER COLUMN ${this.fmtColName(k)} SET NOT NULL`);
268
268
  }
269
- } else {
270
- if (inf[k].is_nullable === 'NO') {
271
- // 难以恢复为 Nullable,跳过
272
- }
273
269
  }
274
270
  }
275
271
 
276
272
  // 7. Drop Not Exist (Force Mode)
277
273
  if (dropNotExistCol) {
278
274
  for (let dbColName in inf) {
279
- // 如果代码中没定义,且不是刚改名的
280
275
  if (!def.columns[dbColName] && !renameTable[dbColName]) {
281
-
282
276
  const dbCol = inf[dbColName];
283
-
284
- // [核心逻辑] 如果是数据库层面的虚拟列 (is_generated == 'ALWAYS'),豁免,不删除
285
277
  if (dbCol.is_generated === 'ALWAYS') {
286
278
  if (debug) console.log(`[NeoPG] Ignoring DB-only generated column: ${dbColName}`);
287
279
  continue;
288
280
  }
289
-
290
- // [额外建议] 如果是 identity 列 (自增列的一种新形式),通常也建议豁免,防止误删
291
- // if (dbCol.is_identity === 'YES') continue;
292
-
293
281
  if (debug) console.log(`Deleting unused column: ${dbColName}`);
294
282
  await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(dbColName)}`);
295
283
  }
296
284
  }
297
285
  }
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
- } */
303
286
  }
304
287
 
305
288
  // --- 索引同步 ---
306
289
  static async syncIndex(sql, def, schema, curTableName, debug) {
307
- // 假设索引定义在 def.rawSchema.index (数组)
308
- // ModelDef 需要暴露这个属性,或 def.indices
309
290
  const indices = def.index || [];
310
291
  if (!Array.isArray(indices)) return;
311
292
 
312
293
  for (const indname of indices) {
313
- // 检查 removeIndex 配置
314
294
  const removeIndex = def.removeIndex || [];
315
295
  if (removeIndex.includes(indname)) continue;
316
296
 
317
- // 检查列是否存在
318
297
  if (!this._checkColumnsExist(indname, def)) {
319
298
  console.error(`Index ${indname} 包含不存在的列,跳过。`);
320
299
  continue;
321
300
  }
322
301
 
323
- // 检查索引是否存在
324
302
  const idxCols = indname.split(',').map(s=>s.trim()).filter(s=>s);
325
303
  const idxNamePart = idxCols.join('_');
326
304
  const targetIdxName = `${def.tableName}_${idxNamePart}_idx`;
327
305
 
328
- // 使用 pg_indexes 查询
329
- // 注意:pg_indexes 不支持 unsafe 拼 schema,只能查 schemaname 列
330
306
  const exist = await sql`
331
307
  SELECT * FROM pg_indexes
332
308
  WHERE tablename = ${def.tableName}
@@ -335,11 +311,6 @@ class SchemaSync {
335
311
  `;
336
312
 
337
313
  if (exist.length > 0) continue;
338
-
339
- // 创建索引
340
- // 支持 using gin 等 (这里简化处理,假设无特殊 using)
341
- // 你的原代码有 indexType 检测,这里简单复刻
342
- // let ind_using = ...
343
314
  await sql.unsafe(`CREATE INDEX ON ${curTableName} (${idxCols.map(c=>this.fmtColName(c)).join(',')})`);
344
315
  }
345
316
  }
@@ -348,12 +319,29 @@ class SchemaSync {
348
319
  const uniques = def.unique || [];
349
320
  if (!Array.isArray(uniques)) return;
350
321
 
322
+ const pkSet = new Set();
323
+ if (Array.isArray(def.primaryKey)) {
324
+ def.primaryKey.forEach(k => pkSet.add(k));
325
+ } else if (def.primaryKey) {
326
+ pkSet.add(def.primaryKey);
327
+ }
328
+
351
329
  for (const indname of uniques) {
352
330
  if (!this._checkColumnsExist(indname, def)) continue;
353
331
 
354
332
  const idxCols = indname.split(',').map(s=>s.trim()).filter(s=>s);
333
+
334
+ // 监测是否等于主键
335
+ if (pkSet.size > 0 && idxCols.length === pkSet.size) {
336
+ const isPk = idxCols.every(col => pkSet.has(col));
337
+ if (isPk) {
338
+ if (debug) console.log(`[NeoPG] Unique '${indname}' matches Primary Key. Skipping.`);
339
+ continue;
340
+ }
341
+ }
342
+
355
343
  const idxNamePart = idxCols.join('_');
356
- const targetIdxName = `${def.tableName}_${idxNamePart}_idx`; // Unique 索引命名通常也遵循此规则,或有 _key 后缀,这里假设一致
344
+ const targetIdxName = `${def.tableName}_${idxNamePart}_idx`;
357
345
 
358
346
  const exist = await sql`
359
347
  SELECT * FROM pg_indexes
@@ -369,7 +357,6 @@ class SchemaSync {
369
357
  }
370
358
 
371
359
  static async autoRemoveIndex(sql, def, schema, tableName, debug) {
372
- // 1. 获取当前所有索引
373
360
  const allIdx = await sql`
374
361
  SELECT indexname FROM pg_indexes
375
362
  WHERE tablename = ${tableName}
@@ -381,7 +368,6 @@ class SchemaSync {
381
368
 
382
369
  const currentIdxNames = allIdx.map(i => i.indexname);
383
370
 
384
- // 2. 计算应该保留的索引名
385
371
  const indices = def.index || [];
386
372
  const uniques = def.unique || [];
387
373
 
@@ -391,7 +377,6 @@ class SchemaSync {
391
377
  indices.forEach(n => keepSet.add(makeName(n)));
392
378
  uniques.forEach(n => keepSet.add(makeName(n)));
393
379
 
394
- // 3. 差集删除
395
380
  for (const idxName of currentIdxNames) {
396
381
  if (!keepSet.has(idxName)) {
397
382
  if (debug) console.log('Auto removing index:', idxName);
@@ -402,56 +387,38 @@ class SchemaSync {
402
387
 
403
388
  // --- 外键同步 ---
404
389
  static async syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options) {
405
- // 1. 收集定义中的外键
406
- // 格式: { fkName: "xxx", createSql: "..." }
407
390
  let targetFKs = new Map();
408
- const qtag = randstring(8);
409
-
391
+
410
392
  for (let k in def.columns) {
411
393
  const col = def.columns[k];
412
394
  if (!col.ref) continue;
413
395
 
414
- // 解析 ref: "ModelName:colName"
415
396
  const [refModelName, refColName] = this._parseRef(col.ref, k);
416
-
417
- // 查找被引用的 Model 定义
418
- // 假设 ctx.registry.get 返回 { Class, def }
419
397
  const targetModelItem = ctx.registry.get(refModelName);
420
-
421
398
  let targetTableName = '';
422
399
 
423
400
  if (targetModelItem) {
424
401
  targetTableName = targetModelItem.def.tableName;
425
- // --- [关键] 递归同步 ---
426
- // 在创建外键之前,确保目标表已经存在且结构最新
427
402
  if (debug) console.log(`[Recursive Sync] Triggered by FK: ${def.modelName} -> ${refModelName}`);
428
-
429
403
  await this.execute(sql, targetModelItem.def, ctx, options);
430
404
  } else {
431
- // 目标模型未注册,可能是直接引用的表名 (Fallback)
432
- if (debug) console.warn(`[NeoPG] Referenced model '${refModelName}' not found in registry. Using as table name.`);
433
405
  targetTableName = refModelName.toLowerCase();
434
406
  }
435
407
 
436
- // 准备外键 SQL
437
408
  const fkName = `${def.tableName}_${k}_fkey`;
438
-
439
- // 构建 REFERENCES 子句
440
409
  let refSql = `REFERENCES ${schema}.${targetTableName} (${refColName})`;
441
410
 
442
411
  if (col.refActionUpdate) refSql += ` ON UPDATE ${col.refActionUpdate}`;
443
- else refSql += ` ON UPDATE CASCADE`; // 默认
412
+ else refSql += ` ON UPDATE CASCADE`;
444
413
 
445
414
  if (col.refActionDelete) refSql += ` ON DELETE ${col.refActionDelete}`;
446
- else refSql += ` ON DELETE CASCADE`; // 默认
415
+ else refSql += ` ON DELETE CASCADE`;
447
416
 
448
417
  targetFKs.set(fkName, { col: k, sql: refSql, changed: col.changed });
449
418
  }
450
419
 
451
- // 2. 获取数据库现有外键
452
420
  const existFKs = new Set();
453
421
  if (targetFKs.size > 0 && schemaOid) {
454
- // 构建 IN 查询
455
422
  const names = Array.from(targetFKs.keys());
456
423
  const rows = await sql`
457
424
  SELECT conname FROM pg_constraint
@@ -462,9 +429,7 @@ class SchemaSync {
462
429
  rows.forEach(r => existFKs.add(r.conname));
463
430
  }
464
431
 
465
- // 3. 同步
466
432
  for (const [fkName, conf] of targetFKs) {
467
- // 如果变更了列类型,必须先删后加
468
433
  if (existFKs.has(fkName) && conf.changed) {
469
434
  await sql.unsafe(`ALTER TABLE ${curTableName} DROP CONSTRAINT ${fkName}`);
470
435
  existFKs.delete(fkName);
@@ -481,11 +446,9 @@ class SchemaSync {
481
446
  // --- 辅助方法 ---
482
447
 
483
448
  static fmtColName(col) {
484
- // 简单处理引用
485
449
  if (forbidColumns.quote.includes(col.toLowerCase())) {
486
450
  return `"${col}"`;
487
451
  }
488
-
489
452
  return `"${col}"`;
490
453
  }
491
454
 
@@ -502,8 +465,6 @@ class SchemaSync {
502
465
  return f.data_type === real_type;
503
466
  }
504
467
 
505
- // 括号解析
506
- // 原逻辑 _parseBrackets
507
468
  const idx = col.type.indexOf('(');
508
469
  const brackets = idx > 0 ? col.type.substring(idx).trim() : '';
509
470
 
@@ -511,16 +472,14 @@ class SchemaSync {
511
472
  return `${f.data_type}(${f.character_maximum_length})` === `${real_type}${brackets}`;
512
473
  }
513
474
 
514
- // numeric(p,s)
515
475
  if (f.data_type === 'numeric' || f.data_type === 'decimal') {
516
- // 注意 PG 返回的 precision 可能是 null
517
476
  const p = f.numeric_precision;
518
477
  const s = f.numeric_scale;
519
- if (!p) return `${real_type}${brackets}` === real_type; // 无精度对比
478
+ if (!p) return `${real_type}${brackets}` === real_type;
520
479
  return `${f.data_type}(${p},${s})` === `${real_type}${brackets}`;
521
480
  }
522
481
 
523
- return false; // Fallback
482
+ return false;
524
483
  }
525
484
 
526
485
  static _checkColumnsExist(colsStr, def) {
@@ -534,12 +493,10 @@ class SchemaSync {
534
493
  static _parseRef(refstr, curColumn) {
535
494
  if (refstr.includes(':')) {
536
495
  const parts = refstr.split(':')
537
- // 处理 Model:col 格式,取最后一部分做 col,前面做 Model
538
496
  const col = parts.pop()
539
497
  const model = parts.join(':')
540
498
  return [model, col]
541
499
  }
542
-
543
500
  return [refstr, curColumn]
544
501
  }
545
502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neopg",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "orm for postgres",
5
5
  "keywords": [
6
6
  "neopg",
package/test/test-db.js CHANGED
@@ -29,7 +29,9 @@ const User = {
29
29
  },
30
30
 
31
31
  mobile: {
32
- type: dataTypes.STRING(16)
32
+ type: dataTypes.STRING(16),
33
+ //type: dataTypes.BIGINT,
34
+ force: true
33
35
  },
34
36
 
35
37
  mobile_state: {
@@ -133,7 +135,7 @@ const User = {
133
135
  //唯一索引
134
136
  unique: [
135
137
  'username',
136
- 'email'
138
+ 'email', 'id'
137
139
  ]
138
140
  }
139
141