feat(database): append child collection name after eager load (#1978)

* test: eager load with inherit collection name

* feat: append child collection name after eager load

* chore: call build eager loading tree
This commit is contained in:
ChengLei Shao 2023-06-06 11:30:35 +08:00 committed by GitHub
parent bc00bd161a
commit 57d47371da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 31 deletions

View File

@ -198,6 +198,7 @@ describe('Eager loading tree', () => {
model: User.model, model: User.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
await eagerLoadingTree.load(users.map((item) => item.id)); await eagerLoadingTree.load(users.map((item) => item.id));
@ -248,6 +249,7 @@ describe('Eager loading tree', () => {
model: User.model, model: User.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
await eagerLoadingTree.load([1, 2]); await eagerLoadingTree.load([1, 2]);
@ -298,6 +300,7 @@ describe('Eager loading tree', () => {
model: User.model, model: User.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
await eagerLoadingTree.load(users.map((item) => item.id)); await eagerLoadingTree.load(users.map((item) => item.id));
@ -353,6 +356,7 @@ describe('Eager loading tree', () => {
model: Post.model, model: Post.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
await eagerLoadingTree.load([1, 2]); await eagerLoadingTree.load([1, 2]);
@ -415,6 +419,7 @@ describe('Eager loading tree', () => {
model: Post.model, model: Post.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
await eagerLoadingTree.load([1, 2]); await eagerLoadingTree.load([1, 2]);
const root = eagerLoadingTree.root; const root = eagerLoadingTree.root;
@ -517,6 +522,7 @@ describe('Eager loading tree', () => {
model: User.model, model: User.model,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: db,
}); });
expect(eagerLoadingTree.root.children).toHaveLength(1); expect(eagerLoadingTree.root.children).toHaveLength(1);

View File

@ -70,6 +70,64 @@ pgOnly()('collection inherits', () => {
expect(db.referenceMap.getReferences('root-target')).toHaveLength(1); expect(db.referenceMap.getReferences('root-target')).toHaveLength(1);
}); });
it('should append collection name in eager load', async () => {
const rootCollection = db.collection({
name: 'assoc',
fields: [{ name: 'name', type: 'string' }],
});
const childCollection = db.collection({
name: 'child',
inherits: ['assoc'],
});
const User = db.collection({
name: 'users',
fields: [
{
name: 'name',
type: 'string',
},
{
name: 'assocs',
type: 'hasMany',
target: 'assoc',
},
],
});
await db.sync();
const child = await childCollection.repository.create({
values: {
name: 'child1',
},
});
await User.repository.create({
values: {
name: 'user1',
assocs: [
{
id: child.get('id'),
},
],
},
});
const users = await User.repository.find({
appends: ['assocs'],
});
const user = users[0];
const assoc = user.get('assocs')[0];
expect(assoc.get('__tableName')).toEqual(childCollection.model.tableName);
expect(assoc.get('__schemaName')).toEqual(childCollection.collectionSchema());
expect(user.get('assocs')[0].get('__collection')).toBe('child');
});
it('should list data filtered by child type', async () => { it('should list data filtered by child type', async () => {
const rootCollection = db.collection({ const rootCollection = db.collection({
name: 'root', name: 'root',

View File

@ -1,5 +1,8 @@
import { Association, HasOne, Includeable, Model, ModelStatic, Transaction } from 'sequelize'; import { Association, HasOne, Includeable, Model, ModelStatic, Transaction } from 'sequelize';
import lodash from 'lodash'; import lodash from 'lodash';
import Database from '../database';
import { OptionsParser } from '../options-parser';
import { appendChildCollectionNameAfterRepositoryFind } from '../listeners/append-child-collection-name-after-repository-find';
interface EagerLoadingNode { interface EagerLoadingNode {
model: ModelStatic<any>; model: ModelStatic<any>;
@ -10,10 +13,29 @@ interface EagerLoadingNode {
parent?: EagerLoadingNode; parent?: EagerLoadingNode;
instances?: Array<Model>; instances?: Array<Model>;
order?: any; order?: any;
inspectInheritAttribute?: boolean;
} }
const pushAttribute = (node, attribute) => {
if (lodash.isArray(node.attributes) && !node.attributes.includes(attribute)) {
node.attributes.push(attribute);
}
};
const EagerLoadingNodeProto = {
afterBuild(db: Database) {
const collection = db.modelCollection.get(this.model);
if (collection && collection.isParent()) {
OptionsParser.appendInheritInspectAttribute(this.attributes.include, collection);
this.inspectInheritAttribute = true;
}
},
};
export class EagerLoadingTree { export class EagerLoadingTree {
public root: EagerLoadingNode; public root: EagerLoadingNode;
db: Database;
constructor(root: EagerLoadingNode) { constructor(root: EagerLoadingNode) {
this.root = root; this.root = root;
@ -24,23 +46,24 @@ export class EagerLoadingTree {
rootAttributes: Array<string>; rootAttributes: Array<string>;
rootOrder?: any; rootOrder?: any;
includeOption: Includeable | Includeable[]; includeOption: Includeable | Includeable[];
db: Database;
}): EagerLoadingTree { }): EagerLoadingTree {
const { model, rootAttributes, includeOption } = options; const { model, rootAttributes, includeOption, db } = options;
const root = { const buildNode = (node) => {
Object.setPrototypeOf(node, EagerLoadingNodeProto);
node.afterBuild(db);
return node;
};
const root = buildNode({
model, model,
association: null, association: null,
rawAttributes: lodash.cloneDeep(rootAttributes), rawAttributes: lodash.cloneDeep(rootAttributes),
attributes: lodash.cloneDeep(rootAttributes), attributes: lodash.cloneDeep(rootAttributes),
order: options.rootOrder, order: options.rootOrder,
children: [], children: [],
}; });
const pushAttribute = (node, attribute) => {
if (lodash.isArray(node.attributes) && !node.attributes.includes(attribute)) {
node.attributes.push(attribute);
}
};
const traverseIncludeOption = (includeOption, eagerLoadingTreeParent) => { const traverseIncludeOption = (includeOption, eagerLoadingTreeParent) => {
const includeOptions = lodash.castArray(includeOption); const includeOptions = lodash.castArray(includeOption);
@ -59,14 +82,14 @@ export class EagerLoadingTree {
const association = eagerLoadingTreeParent.model.associations[include.association]; const association = eagerLoadingTreeParent.model.associations[include.association];
const associationType = association.associationType; const associationType = association.associationType;
const child = { const child = buildNode({
model: association.target, model: association.target,
association, association,
rawAttributes: lodash.cloneDeep(include.attributes), rawAttributes: lodash.cloneDeep(include.attributes),
attributes: lodash.cloneDeep(include.attributes), attributes: lodash.cloneDeep(include.attributes),
parent: eagerLoadingTreeParent, parent: eagerLoadingTreeParent,
children: [], children: [],
}; });
if (associationType == 'HasOne' || associationType == 'HasMany') { if (associationType == 'HasOne' || associationType == 'HasMany') {
const { sourceKey, foreignKey } = association; const { sourceKey, foreignKey } = association;
@ -97,7 +120,9 @@ export class EagerLoadingTree {
traverseIncludeOption(includeOption, root); traverseIncludeOption(includeOption, root);
return new EagerLoadingTree(root); const tree = new EagerLoadingTree(root);
tree.db = db;
return tree;
} }
async load(pks: Array<string | number>, transaction?: Transaction) { async load(pks: Array<string | number>, transaction?: Transaction) {
@ -297,7 +322,17 @@ export class EagerLoadingTree {
await loadRecursive(this.root, pks); await loadRecursive(this.root, pks);
const appendChildCollectionName = appendChildCollectionNameAfterRepositoryFind(this.db);
const setInstanceAttributes = (node) => { const setInstanceAttributes = (node) => {
if (node.inspectInheritAttribute) {
appendChildCollectionName({
findOptions: {},
data: node.instances,
dataCollection: this.db.modelCollection.get(node.model),
});
}
const nodeRawAttributes = node.rawAttributes; const nodeRawAttributes = node.rawAttributes;
if (!lodash.isArray(nodeRawAttributes)) { if (!lodash.isArray(nodeRawAttributes)) {

View File

@ -35,6 +35,23 @@ export class OptionsParser {
this.context = context; this.context = context;
} }
static appendInheritInspectAttribute(include, collection): any {
include.push([
Sequelize.literal(`(select relname from pg_class where pg_class.oid = "${collection.name}".tableoid)`),
'__tableName',
]);
include.push([
Sequelize.literal(`
(SELECT n.nspname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.oid = "${collection.name}".tableoid)
`),
'__schemaName',
]);
}
isAssociation(key: string) { isAssociation(key: string) {
return this.model.associations[key] !== undefined; return this.model.associations[key] !== undefined;
} }
@ -109,23 +126,6 @@ export class OptionsParser {
return filterParams; return filterParams;
} }
protected inheritFromSubQuery(include): any {
include.push([
Sequelize.literal(`(select relname from pg_class where pg_class.oid = "${this.collection.name}".tableoid)`),
'__tableName',
]);
include.push([
Sequelize.literal(`
(SELECT n.nspname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.oid = "${this.collection.name}".tableoid)
`),
'__schemaName',
]);
}
protected parseFields(filterParams: any) { protected parseFields(filterParams: any) {
const appends = this.options?.appends || []; const appends = this.options?.appends || [];
const except = []; const except = [];
@ -142,13 +142,13 @@ export class OptionsParser {
}; // out put all fields by default }; // out put all fields by default
if (this.collection.isParent()) { if (this.collection.isParent()) {
this.inheritFromSubQuery(attributes.include); OptionsParser.appendInheritInspectAttribute(attributes.include, this.collection);
} }
if (this.options?.fields) { if (this.options?.fields) {
attributes = []; attributes = [];
if (this.collection.isParent()) { if (this.collection.isParent()) {
this.inheritFromSubQuery(attributes); OptionsParser.appendInheritInspectAttribute(attributes, this.collection);
} }
// 将fields拆分为 attributes 和 appends // 将fields拆分为 attributes 和 appends

View File

@ -65,6 +65,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
rootOrder: findOptions.order, rootOrder: findOptions.order,
db: this.db,
}); });
await eagerLoadingTree.load( await eagerLoadingTree.load(

View File

@ -71,6 +71,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
model: this.targetModel, model: this.targetModel,
rootAttributes: findOptions.attributes, rootAttributes: findOptions.attributes,
includeOption: findOptions.include, includeOption: findOptions.include,
db: this.db,
}); });
await eagerLoadingTree.load([templateModel.get(this.targetModel.primaryKeyAttribute)], transaction); await eagerLoadingTree.load([templateModel.get(this.targetModel.primaryKeyAttribute)], transaction);

View File

@ -367,6 +367,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
rootAttributes: opts.attributes, rootAttributes: opts.attributes,
includeOption: opts.include, includeOption: opts.include,
rootOrder: opts.order, rootOrder: opts.order,
db: this.database,
}); });
await eagerLoadingTree.load( await eagerLoadingTree.load(