import { Op, Utils, Sequelize } from 'sequelize'; import Model, { ModelCtor } from './model'; import _ from 'lodash'; const op = new Map(); for (const key in Op) { op.set(key, Op[key]); const val = Utils.underscoredIf(key, true); op.set(val, Op[key]); op.set(val.replace(/_/g, ''), Op[key]); } interface ToWhereContext { Model?: ModelCtor | Model | typeof Model; associations?: any; dialect?: string; ctx?: any; } export function toWhere(options: any, context: ToWhereContext = {}) { if (options === null || typeof options !== 'object') { return options; } if (Array.isArray(options)) { return options.map((item) => toWhere(item, context)); } const { Model, associations = {}, ctx, dialect } = context; const items = {}; // 先处理「点号」的问题 for (const key in options) { _.set(items, key, options[key]); } const values = {}; for (const key in items) { if (associations[key]) { values['$__include'] = values['$__include'] || {} values['$__include'][key] = toWhere(items[key], { ...context, Model: associations[key].target, associations: associations[key].target.associations, }); } else if (Model && Model.options.scopes && Model.options.scopes[key]) { values['$__scopes'] = values['$__scopes'] || []; const scope = Model.options.scopes[key]; if (typeof scope === 'function') { values['$__scopes'].push({ method: [key, items[key], ctx] }); } else { values['$__scopes'].push(key); } } else { // TODO: to fix same op key as field name values[op.has(key) ? op.get(key) : key] = toWhere(items[key], context); } } return values; } interface ToIncludeContext { Model?: ModelCtor | Model | typeof Model; sourceAlias?: string; associations?: any; dialect?: string; ctx?: any } export function toInclude(options: any, context: ToIncludeContext = {}) { const { fields = [] } = options; const { Model, sourceAlias, associations = {}, ctx, dialect } = context; let where = options.where || {}; if (options.filter) { where = toWhere(options.filter, { Model, associations, ctx, }) || {}; } const includeWhere = Utils.cloneDeep(where.$__include||{}); const scopes = Utils.cloneDeep(where.$__scopes||[]); delete where.$__include; delete where.$__scopes; const attributes = { only: [], except: [], appends: [], }; const include = new Map(); const children = new Map(); const items = Array.isArray(fields) ? { only: fields } : fields; let sort = options.sort; if (sort && typeof sort === 'string') { sort = sort.split(','); } const order = []; if (Array.isArray(sort) && sort.length > 0) { items.appends = items.appends || []; sort.forEach(key => { if (Array.isArray(key)) { order.push(key); } else { const direction = key[0] === '-' ? 'DESC' : 'ASC'; const field = key.replace(/^-/, ''); // TODO(verify): 理论上只是排序并不一定要输出相关字段 // items.appends.push(field); // TODO: 暂时只支持主表排序,后续需要按`.`分隔符拆分 field 为关联排序 order.push([field, direction]); } }); } if (Array.isArray(items.only) && items.only.length > 0) { items.only.forEach(field => { const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); const col = arr.shift(); // 内嵌的情况 if (arr.length > 0) { if (!children.has(col)) { children.set(col, { fields: { only: [arr], except: [], appends: [], }, }); } else { children.get(col).fields.only.push(arr); } return; } // 关系字段 if (associations[col]) { const includeItem: any = { association: col, }; if (includeWhere[col]) { includeItem.where = includeWhere[col]; } include.set(col, includeItem); return; } const matches: Array = /(.+)\_count$/.exec(col); if (matches && associations[matches[1]]) { attributes.only.push(Model.withCountAttribute({ association: matches[1], sourceAlias: sourceAlias })); } else { attributes.only.push(col); } }); } if (Array.isArray(items.appends) && items.appends.length > 0) { items.appends.forEach(field => { const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); const col = arr.shift(); // 内嵌的情况 if (arr.length > 0) { if (!children.has(col)) { children.set(col, { fields: { only: [], except: [], appends: [arr], }, }); } else { children.get(col).fields.appends.push(arr); } return; } // 关系字段 if (associations[col]) { const includeItem: any = { association: col, }; if (includeWhere[col]) { includeItem.where = includeWhere[col]; } include.set(col, includeItem); return; } const matches: Array = /(.+)\_count$/.exec(col); if (matches && associations[matches[1]]) { attributes.appends.push(Model.withCountAttribute({ association: matches[1], sourceAlias: sourceAlias })); } else { attributes.appends.push(col); } }); } if (Array.isArray(items.except) && items.except.length > 0) { items.except.forEach(field => { const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); const col = arr.shift(); // 内嵌的情况 if (arr.length > 0) { if (!children.has(col)) { children.set(col, { where: includeWhere[col], fields: { only: [], except: [arr], appends: [], }, }); } else { children.get(col).fields.except.push(arr); } return; } // 黑名单里只有字段 attributes.except.push(col); }); } for (const whereKey in includeWhere) { if (children.has(whereKey)) { children.get(whereKey).where = includeWhere[whereKey]; } else { children.set(whereKey, { association: whereKey, fields: [], where: includeWhere[whereKey], }); } } for (const [key, child] of children) { const result = toInclude(child, { ...context, Model: associations[key].target, sourceAlias: key, associations: associations[key].target.associations, }); const item: any = { association: key, } if (result.attributes) { item.attributes = result.attributes; } if (result.include) { item.include = result.include; } if (result.where) { item.where = result.where; } if (result.scopes) { item.model = associations[key].target.scope(result.scopes); } include.set(key, item); } const data:any = {}; // 存在黑名单时 if (attributes.except.length > 0) { data.attributes = { include: attributes.appends, exclude: attributes.except, }; } // 存在白名单时 else if (attributes.only.length > 0) { data.attributes = [...attributes.only, ...attributes.appends]; } // 只有附加字段时 else if (attributes.appends.length > 0) { data.attributes = { include: attributes.appends, }; } if (include.size > 0) { if (!data.attributes) { data.attributes = []; } data.include = Array.from(include.values()); } if (Object.keys(where).length > 0) { data.where = where; } if (scopes.length > 0) { data.scopes = scopes; } if (order.length > 0) { data.order = order; } return data; } export function requireModule(module: any) { if (typeof module === 'string') { module = require(module); } if (typeof module !== 'object') { return module; } return module.__esModule ? module.default : module; }