2020-10-24 15:34:43 +08:00
|
|
|
|
import {
|
2020-12-11 15:41:03 +08:00
|
|
|
|
Model as SequelizeModel, Op, Sequelize, ProjectionAlias, Utils, SaveOptions
|
2020-10-24 15:34:43 +08:00
|
|
|
|
} from 'sequelize';
|
|
|
|
|
import Database from './database';
|
2020-12-10 14:47:13 +08:00
|
|
|
|
import {
|
|
|
|
|
getDataTypeKey,
|
|
|
|
|
HASONE,
|
|
|
|
|
HASMANY,
|
|
|
|
|
BELONGSTO,
|
|
|
|
|
BELONGSTOMANY,
|
|
|
|
|
} from './fields';
|
2020-10-24 15:34:43 +08:00
|
|
|
|
import { toInclude } from './utils';
|
|
|
|
|
|
|
|
|
|
export interface ApiJsonOptions {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 字段
|
|
|
|
|
*
|
|
|
|
|
* 数组式:
|
|
|
|
|
* ['col', 'association.col1', 'association_count'],
|
|
|
|
|
*
|
|
|
|
|
* 白名单:
|
|
|
|
|
* {
|
|
|
|
|
* only: ['col1'],
|
|
|
|
|
* appends: ['association_count'],
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* 黑名单:
|
|
|
|
|
* {
|
|
|
|
|
* except: ['col1'],
|
|
|
|
|
* appends: ['association_count'],
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
fields?: string[] | {
|
|
|
|
|
only?: string[];
|
|
|
|
|
appends?: string[];
|
|
|
|
|
} | {
|
|
|
|
|
except?: string[];
|
|
|
|
|
appends?: string[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 过滤
|
|
|
|
|
*
|
|
|
|
|
* 常规用法:
|
|
|
|
|
* {
|
|
|
|
|
* col1: {
|
|
|
|
|
* $eq: 'val1'
|
|
|
|
|
* },
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* scope 的用法(如果 scope 与 col 同名,只会执行 scope):
|
|
|
|
|
* {
|
|
|
|
|
* scope1: value
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* json 数据 & 关系数据,可以用点号:
|
|
|
|
|
* {
|
|
|
|
|
* 'association.col1': {
|
|
|
|
|
* $eq: 'val1'
|
|
|
|
|
* },
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* meta 为 json 字段时
|
|
|
|
|
* {
|
|
|
|
|
* 'meta.key': {
|
|
|
|
|
* $eq: 'val1'
|
|
|
|
|
* },
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* json 数据 & 关系数据的查询也可以不用点号:
|
|
|
|
|
* {
|
|
|
|
|
* association: {
|
|
|
|
|
* col1: {
|
|
|
|
|
* $eq: 'val1'
|
|
|
|
|
* },
|
|
|
|
|
* },
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
filter?: any;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 排序
|
|
|
|
|
*
|
|
|
|
|
* TODO
|
|
|
|
|
*
|
|
|
|
|
* ['col1', '-col2', 'association.col1', '-association.col2']
|
|
|
|
|
*/
|
|
|
|
|
sort?: any;
|
|
|
|
|
|
2020-11-19 23:30:30 +08:00
|
|
|
|
/**
|
|
|
|
|
* 页码
|
|
|
|
|
*/
|
|
|
|
|
page?: number;
|
|
|
|
|
perPage?: number;
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
context?: any;
|
|
|
|
|
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface WithCountAttributeOptions {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关系名
|
|
|
|
|
*/
|
|
|
|
|
association: string;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SourceModel 别名
|
|
|
|
|
*
|
|
|
|
|
* 在 include 里使用时,需要指定,一般与 include 的 association 同名
|
|
|
|
|
*
|
|
|
|
|
* include: {
|
|
|
|
|
* association: 'user', // Post.belongsTo(User)
|
|
|
|
|
* attributes: [
|
|
|
|
|
* User.withCountAttribute({
|
|
|
|
|
* association: 'posts',
|
|
|
|
|
* sourceAlias: 'user', // 内嵌时,需要指定 source 别名
|
|
|
|
|
* })
|
|
|
|
|
* ]
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
sourceAlias?: string;
|
|
|
|
|
|
|
|
|
|
where?: any;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 别名,默认为 association_count
|
|
|
|
|
*/
|
|
|
|
|
alias?: string;
|
|
|
|
|
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 16:49:46 +08:00
|
|
|
|
export const DEFAULT_OFFSET = 0;
|
|
|
|
|
export const DEFAULT_LIMIT = 100;
|
|
|
|
|
export const MAX_LIMIT = 500;
|
|
|
|
|
|
2021-01-02 22:30:13 +08:00
|
|
|
|
export interface UpdateAssociationOptions extends SaveOptions {
|
|
|
|
|
context?: any;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* Model 相关
|
|
|
|
|
*
|
|
|
|
|
* TODO: 自定义 model 时的提示问题
|
|
|
|
|
*/
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
export abstract class Model extends SequelizeModel {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 防止 ts 报错提示
|
|
|
|
|
*/
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当前 Model 的 database
|
|
|
|
|
*
|
|
|
|
|
* 与 Model.sequelize 对应,database 也用了 public static readonly
|
|
|
|
|
*/
|
|
|
|
|
public static database: Database;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 供 model 实例访问的 database
|
|
|
|
|
*/
|
|
|
|
|
get database(): Database {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return this.constructor.database;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* sub query 关联数据的数量
|
|
|
|
|
*
|
|
|
|
|
* TODO: 关联字段暂不支持主键以外的字段
|
|
|
|
|
*
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
static withCountAttribute(options?: string | WithCountAttributeOptions): (string | ProjectionAlias) {
|
|
|
|
|
if (typeof options === 'string') {
|
|
|
|
|
options = { association: options };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { sourceAlias, association, where = {}, alias, ...restOptions } = options;
|
|
|
|
|
const associator = this.associations[association];
|
|
|
|
|
const table = this.database.getTable(this.name);
|
|
|
|
|
const field = table.getField(association);
|
|
|
|
|
const { targetKey, otherKey, foreignKey, sourceKey } = field.options as any;
|
|
|
|
|
|
|
|
|
|
if (associator.associationType === 'HasMany') {
|
|
|
|
|
where[foreignKey as string] = {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
[Op.eq]: Sequelize.col(`${sourceAlias || this.name}.${sourceKey}`),
|
2020-10-24 15:34:43 +08:00
|
|
|
|
};
|
|
|
|
|
} else if (associator.associationType === 'BelongsToMany') {
|
|
|
|
|
where[targetKey] = {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
[Op.in]: Sequelize.literal(`(${associator.through.model.selectQuery({
|
|
|
|
|
attributes: [otherKey],
|
|
|
|
|
where: {
|
|
|
|
|
[foreignKey]: {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
[Op.eq]: Sequelize.col(`${sourceAlias || this.name}.${sourceKey}`),
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
|
|
|
|
// @ts-ignore
|
2021-03-28 13:34:51 +08:00
|
|
|
|
...(associator.through.scope || {}),
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
|
|
|
|
})})`),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let countLiteral = 'count(*)';
|
|
|
|
|
|
|
|
|
|
if (this.database.sequelize.getDialect() === 'postgres') {
|
|
|
|
|
countLiteral = 'cast(count(*) as integer)';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const attribute = [
|
|
|
|
|
Sequelize.literal(
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
`(${associator.target.selectQuery({
|
|
|
|
|
...restOptions,
|
|
|
|
|
attributes: [[Sequelize.literal(countLiteral), 'count']],
|
|
|
|
|
where: {
|
|
|
|
|
// @ts-ignore
|
2021-03-28 13:34:51 +08:00
|
|
|
|
...where, ...(associator.scope || {}),
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
|
|
|
|
})})`
|
|
|
|
|
),
|
|
|
|
|
alias || Utils.underscoredIf(`${association}Count`, this.options.underscored),
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
2021-01-23 17:12:09 +08:00
|
|
|
|
return attribute as unknown as ProjectionAlias;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当前 Model 的 SQL
|
|
|
|
|
*
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
static selectQuery(options = {}): string {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return this.queryGenerator.selectQuery(
|
2021-03-28 13:34:51 +08:00
|
|
|
|
this.getTableName(),
|
2020-10-24 15:34:43 +08:00
|
|
|
|
options,
|
|
|
|
|
this,
|
|
|
|
|
).replace(/;$/, '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static parseApiJson(options: ApiJsonOptions) {
|
2020-11-19 23:30:30 +08:00
|
|
|
|
const { fields, filter, sort, context, page, perPage } = options;
|
2021-03-28 13:34:51 +08:00
|
|
|
|
const data = toInclude({ fields, filter, sort }, {
|
2020-11-23 16:49:46 +08:00
|
|
|
|
model: this,
|
2020-10-24 15:34:43 +08:00
|
|
|
|
associations: this.associations,
|
|
|
|
|
dialect: this.sequelize.getDialect(),
|
|
|
|
|
ctx: context,
|
2020-12-28 21:08:13 +08:00
|
|
|
|
database: this.database,
|
2020-10-24 15:34:43 +08:00
|
|
|
|
});
|
2020-11-19 23:30:30 +08:00
|
|
|
|
if (page || perPage) {
|
2020-11-23 16:49:46 +08:00
|
|
|
|
data.limit = perPage === -1 ? MAX_LIMIT : Math.min(perPage || DEFAULT_LIMIT, MAX_LIMIT);
|
|
|
|
|
data.offset = data.limit * (page > 0 ? page - 1 : DEFAULT_OFFSET);
|
2020-11-19 23:30:30 +08:00
|
|
|
|
}
|
2020-10-24 15:34:43 +08:00
|
|
|
|
if (data.attributes && data.attributes.length === 0) {
|
|
|
|
|
delete data.attributes;
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-12 16:38:08 +08:00
|
|
|
|
getValuesByFieldNames(scope = []) {
|
2020-12-11 15:41:03 +08:00
|
|
|
|
const table = this.database.getTable(this.constructor.name);
|
2020-12-12 16:38:08 +08:00
|
|
|
|
const Model = table.getModel();
|
2020-12-11 15:41:03 +08:00
|
|
|
|
const associations = table.getAssociations();
|
|
|
|
|
const where = {};
|
|
|
|
|
scope.forEach(col => {
|
|
|
|
|
const association = associations.get(col);
|
|
|
|
|
const dataKey = association && association instanceof BELONGSTO
|
|
|
|
|
? association.options.foreignKey
|
|
|
|
|
: col;
|
|
|
|
|
if (!Model.rawAttributes[dataKey]) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const value = this.getDataValue(dataKey);
|
|
|
|
|
if (typeof value !== 'undefined') {
|
|
|
|
|
where[dataKey] = value;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return where;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-02 22:30:13 +08:00
|
|
|
|
async updateSingleAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const {
|
|
|
|
|
fields,
|
|
|
|
|
transaction = await this.sequelize.transaction(),
|
|
|
|
|
...opts
|
|
|
|
|
} = options;
|
|
|
|
|
Object.assign(opts, { transaction });
|
|
|
|
|
|
|
|
|
|
const table = this.database.getTable(this.constructor.name);
|
|
|
|
|
const association = table.getAssociations().get(key);
|
|
|
|
|
const accessors = association.getAccessors();
|
|
|
|
|
|
2021-01-02 22:30:13 +08:00
|
|
|
|
if (data == null) {
|
|
|
|
|
await this[accessors.set](null, opts);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-04 17:20:08 +08:00
|
|
|
|
if (typeof data === 'number' || typeof data === 'string' || data instanceof SequelizeModel) {
|
|
|
|
|
await this[accessors.set](data, opts);
|
|
|
|
|
} else if (typeof data === 'object') {
|
|
|
|
|
const Target = association.getTargetModel();
|
2021-03-28 13:34:51 +08:00
|
|
|
|
const targetAttribute = association instanceof BELONGSTO
|
|
|
|
|
? association.options.targetKey
|
2020-12-04 17:20:08 +08:00
|
|
|
|
: association.options.sourceKey;
|
|
|
|
|
if (data[targetAttribute]) {
|
2021-07-11 22:20:54 +08:00
|
|
|
|
if (Object.keys(data).length > 0) {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const target = await Target.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
[targetAttribute]: data[targetAttribute],
|
|
|
|
|
},
|
|
|
|
|
transaction
|
|
|
|
|
});
|
2021-07-11 22:20:54 +08:00
|
|
|
|
if (target) {
|
|
|
|
|
await this[accessors.set](data[targetAttribute], opts);
|
|
|
|
|
await target.update(data, opts);
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
await target.updateAssociations(data, opts);
|
|
|
|
|
} else {
|
|
|
|
|
const t = await this[accessors.create](data, opts);
|
|
|
|
|
await t.updateAssociations(data, opts);
|
|
|
|
|
}
|
2020-12-04 17:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const t = await this[accessors.create](data, opts);
|
|
|
|
|
await t.updateAssociations(data, opts);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!options.transaction) {
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-02 22:30:13 +08:00
|
|
|
|
async updateMultipleAssociation(associationName: string, data: any, options: UpdateAssociationOptions = {}) {
|
|
|
|
|
const items = Array.isArray(data) ? data : data == null ? [] : [data];
|
2020-12-04 17:20:08 +08:00
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
fields,
|
|
|
|
|
transaction = await this.sequelize.transaction(),
|
|
|
|
|
...opts
|
|
|
|
|
} = options;
|
|
|
|
|
Object.assign(opts, { transaction });
|
|
|
|
|
|
|
|
|
|
const table = this.database.getTable(this.constructor.name);
|
|
|
|
|
const association = table.getAssociations().get(associationName);
|
|
|
|
|
const accessors = association.getAccessors();
|
2021-01-02 22:30:13 +08:00
|
|
|
|
|
|
|
|
|
if (!items.length) {
|
|
|
|
|
await this[accessors.set](null, opts);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const Target = association.getTargetModel();
|
|
|
|
|
// 当前表关联 target 表的外键(大部分情况与 target 表主键相同,但可以设置为不同的,要考虑)
|
|
|
|
|
const { targetKey = Target.primaryKeyAttribute } = association.options;
|
|
|
|
|
// target 表的主键
|
|
|
|
|
const targetPk = Target.primaryKeyAttribute;
|
|
|
|
|
const targetKeyIsPk = targetKey === targetPk;
|
|
|
|
|
// 准备设置的关联主键
|
|
|
|
|
const toSetPks = new Set();
|
|
|
|
|
const toSetUks = new Set();
|
2020-12-06 14:28:23 +08:00
|
|
|
|
// 筛选后准备设置的关联主键
|
|
|
|
|
const toSetItems = new Set();
|
2020-12-04 17:20:08 +08:00
|
|
|
|
// 准备添加的关联对象
|
|
|
|
|
const toUpsertObjects = [];
|
|
|
|
|
|
|
|
|
|
// 遍历所有值成员准备数据
|
|
|
|
|
items.forEach(item => {
|
|
|
|
|
if (item instanceof SequelizeModel) {
|
|
|
|
|
if (targetKeyIsPk) {
|
|
|
|
|
toSetPks.add(item.getDataValue(targetPk));
|
|
|
|
|
} else {
|
|
|
|
|
toSetUks.add(item.getDataValue(targetKey));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (typeof item === 'number' || typeof item === 'string') {
|
|
|
|
|
let targetKeyType = getDataTypeKey(Target.rawAttributes[targetKey].type).toLocaleLowerCase();
|
|
|
|
|
if (targetKeyType === 'integer') {
|
|
|
|
|
targetKeyType = 'number';
|
|
|
|
|
}
|
|
|
|
|
// 如果传值类型与之前在 Model 上定义的 targetKey 不同,则报错。
|
|
|
|
|
// 不应兼容定义的 targetKey 不是 primaryKey 却传了 primaryKey 的值的情况。
|
|
|
|
|
if (typeof item !== targetKeyType) {
|
|
|
|
|
throw new Error(`target key type [${typeof item}] does not match to [${targetKeyType}]`);
|
|
|
|
|
}
|
|
|
|
|
if (targetKeyIsPk) {
|
|
|
|
|
toSetPks.add(item);
|
|
|
|
|
} else {
|
|
|
|
|
toSetUks.add(item);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (typeof item === 'object') {
|
|
|
|
|
toUpsertObjects.push(item);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* 仅传关联键处理开始 */
|
2020-12-06 14:28:23 +08:00
|
|
|
|
// 查找已存在的数据
|
|
|
|
|
const byPkExistItems = toSetPks.size ? await Target.findAll({
|
2020-12-04 17:20:08 +08:00
|
|
|
|
...opts,
|
2020-12-06 14:28:23 +08:00
|
|
|
|
// @ts-ignore
|
2020-12-04 17:20:08 +08:00
|
|
|
|
where: {
|
|
|
|
|
[targetPk]: {
|
|
|
|
|
[Op.in]: Array.from(toSetPks)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
attributes: [targetPk]
|
|
|
|
|
}) : [];
|
|
|
|
|
byPkExistItems.forEach(item => {
|
2020-12-06 14:28:23 +08:00
|
|
|
|
toSetItems.add(item);
|
2020-12-04 17:20:08 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-12-06 14:28:23 +08:00
|
|
|
|
const byUkExistItems = toSetUks.size ? await Target.findAll({
|
2020-12-04 17:20:08 +08:00
|
|
|
|
...opts,
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
where: {
|
|
|
|
|
[targetKey]: {
|
|
|
|
|
[Op.in]: Array.from(toSetUks)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
attributes: [targetPk, targetKey]
|
|
|
|
|
}) : [];
|
2020-12-06 14:28:23 +08:00
|
|
|
|
byUkExistItems.forEach(item => {
|
|
|
|
|
toSetItems.add(item);
|
2020-12-04 17:20:08 +08:00
|
|
|
|
});
|
|
|
|
|
/* 仅传关联键处理结束 */
|
|
|
|
|
|
2020-12-06 14:28:23 +08:00
|
|
|
|
const belongsToManyList = [];
|
2020-12-04 17:20:08 +08:00
|
|
|
|
/* 值为对象处理开始 */
|
|
|
|
|
for (const item of toUpsertObjects) {
|
|
|
|
|
let target;
|
|
|
|
|
if (typeof item[targetKey] === 'undefined') {
|
2020-12-11 15:41:03 +08:00
|
|
|
|
target = await this[accessors.create](item, opts);
|
2020-12-04 17:20:08 +08:00
|
|
|
|
} else {
|
2020-12-11 15:41:03 +08:00
|
|
|
|
target = await Target.findOne({
|
|
|
|
|
...opts,
|
2020-12-04 17:20:08 +08:00
|
|
|
|
where: { [targetKey]: item[targetKey] },
|
|
|
|
|
});
|
2020-12-11 15:41:03 +08:00
|
|
|
|
if (!target) {
|
|
|
|
|
target = await this[accessors.create](item, opts);
|
|
|
|
|
} else {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
await target.update(item, opts);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-11 15:41:03 +08:00
|
|
|
|
// TODO(optimize): 此处添加的对象其实已经创建了关联,
|
|
|
|
|
// 但考虑到单条 create 的 hook 要求带上关联键,且后面的 set,
|
|
|
|
|
// 所以仍然交给 set 再调用关联一次。
|
|
|
|
|
toSetItems.add(target);
|
|
|
|
|
|
2020-12-10 14:47:13 +08:00
|
|
|
|
if (association instanceof BELONGSTOMANY) {
|
2020-12-06 14:28:23 +08:00
|
|
|
|
belongsToManyList.push({
|
|
|
|
|
item,
|
|
|
|
|
target
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await target.updateAssociations(item, opts);
|
|
|
|
|
}
|
|
|
|
|
/* 值为对象处理结束 */
|
|
|
|
|
|
|
|
|
|
// 添加所有计算后的关联
|
|
|
|
|
await this[accessors.set](Array.from(toSetItems), opts);
|
|
|
|
|
|
|
|
|
|
// 后处理 belongsToMany 的更新内容
|
|
|
|
|
if (belongsToManyList.length) {
|
2020-12-10 14:47:13 +08:00
|
|
|
|
const ThroughModel = (association as BELONGSTOMANY).getThroughModel();
|
|
|
|
|
const throughName = (association as BELONGSTOMANY).getThroughName();
|
2020-12-06 14:28:23 +08:00
|
|
|
|
|
2020-12-22 17:28:30 +08:00
|
|
|
|
|
2020-12-06 14:28:23 +08:00
|
|
|
|
for (const { item, target } of belongsToManyList) {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const throughValues = item[throughName];
|
2020-12-22 17:28:30 +08:00
|
|
|
|
if (throughValues && typeof throughValues === 'object') {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const { foreignKey, sourceKey, otherKey } = association.options;
|
|
|
|
|
const through = await ThroughModel.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
[foreignKey]: this.get(sourceKey),
|
|
|
|
|
[otherKey]: target.get(targetKey),
|
|
|
|
|
},
|
|
|
|
|
transaction
|
|
|
|
|
});
|
|
|
|
|
await through.update(throughValues, opts);
|
2020-12-22 17:28:30 +08:00
|
|
|
|
// TODO:有 BUG,未知
|
|
|
|
|
// await through.updateAssociations(throughValues, opts);
|
2020-12-04 17:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!options.transaction) {
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-02 22:30:13 +08:00
|
|
|
|
async updateAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const table = this.database.getTable(this.constructor.name);
|
|
|
|
|
const association = table.getAssociations().get(key);
|
|
|
|
|
switch (true) {
|
2020-12-10 14:47:13 +08:00
|
|
|
|
case association instanceof BELONGSTO:
|
|
|
|
|
case association instanceof HASONE:
|
2020-12-04 17:20:08 +08:00
|
|
|
|
return this.updateSingleAssociation(key, data, options);
|
2020-12-10 14:47:13 +08:00
|
|
|
|
case association instanceof HASMANY:
|
|
|
|
|
case association instanceof BELONGSTOMANY:
|
2020-12-04 17:20:08 +08:00
|
|
|
|
return this.updateMultipleAssociation(key, data, options);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 关联数据的更新
|
|
|
|
|
*
|
|
|
|
|
* @param data
|
|
|
|
|
*/
|
2021-01-02 22:30:13 +08:00
|
|
|
|
async updateAssociations(data: any, options: UpdateAssociationOptions = {}) {
|
2020-12-04 17:20:08 +08:00
|
|
|
|
const { transaction = await this.sequelize.transaction() } = options;
|
2021-07-11 22:20:54 +08:00
|
|
|
|
// @ts-ignore 判断 Model.associations 更准确
|
|
|
|
|
for (const key of Object.keys(this.constructor.associations)) {
|
2021-01-02 22:30:13 +08:00
|
|
|
|
// 如果 key 不存在才跳过
|
|
|
|
|
if (!Object.keys(data).includes(key)) {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-12-04 17:20:08 +08:00
|
|
|
|
await this.updateAssociation(key, data[key], {
|
|
|
|
|
...options,
|
|
|
|
|
transaction
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-09 23:57:01 +08:00
|
|
|
|
await this.database.emitAsync('afterUpdateAssociations', this, {
|
2020-12-21 14:15:08 +08:00
|
|
|
|
...options,
|
|
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
// await this.sequelize.runHooks('afterUpdateAssociations', this, options);
|
|
|
|
|
|
2020-12-04 17:20:08 +08:00
|
|
|
|
if (!options.transaction) {
|
|
|
|
|
await transaction.commit();
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ModelCtor 需要为当前 Model 的
|
|
|
|
|
*/
|
|
|
|
|
export type ModelCtor<M extends Model> = typeof Model & { new(): M } & { [key: string]: any };
|
|
|
|
|
|
|
|
|
|
export default Model;
|