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

View File

@ -70,6 +70,64 @@ pgOnly()('collection inherits', () => {
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 () => {
const rootCollection = db.collection({
name: 'root',

View File

@ -1,5 +1,8 @@
import { Association, HasOne, Includeable, Model, ModelStatic, Transaction } from 'sequelize';
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 {
model: ModelStatic<any>;
@ -10,10 +13,29 @@ interface EagerLoadingNode {
parent?: EagerLoadingNode;
instances?: Array<Model>;
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 {
public root: EagerLoadingNode;
db: Database;
constructor(root: EagerLoadingNode) {
this.root = root;
@ -24,23 +46,24 @@ export class EagerLoadingTree {
rootAttributes: Array<string>;
rootOrder?: any;
includeOption: Includeable | Includeable[];
db: Database;
}): 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,
association: null,
rawAttributes: lodash.cloneDeep(rootAttributes),
attributes: lodash.cloneDeep(rootAttributes),
order: options.rootOrder,
children: [],
};
const pushAttribute = (node, attribute) => {
if (lodash.isArray(node.attributes) && !node.attributes.includes(attribute)) {
node.attributes.push(attribute);
}
};
});
const traverseIncludeOption = (includeOption, eagerLoadingTreeParent) => {
const includeOptions = lodash.castArray(includeOption);
@ -59,14 +82,14 @@ export class EagerLoadingTree {
const association = eagerLoadingTreeParent.model.associations[include.association];
const associationType = association.associationType;
const child = {
const child = buildNode({
model: association.target,
association,
rawAttributes: lodash.cloneDeep(include.attributes),
attributes: lodash.cloneDeep(include.attributes),
parent: eagerLoadingTreeParent,
children: [],
};
});
if (associationType == 'HasOne' || associationType == 'HasMany') {
const { sourceKey, foreignKey } = association;
@ -97,7 +120,9 @@ export class EagerLoadingTree {
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) {
@ -297,7 +322,17 @@ export class EagerLoadingTree {
await loadRecursive(this.root, pks);
const appendChildCollectionName = appendChildCollectionNameAfterRepositoryFind(this.db);
const setInstanceAttributes = (node) => {
if (node.inspectInheritAttribute) {
appendChildCollectionName({
findOptions: {},
data: node.instances,
dataCollection: this.db.modelCollection.get(node.model),
});
}
const nodeRawAttributes = node.rawAttributes;
if (!lodash.isArray(nodeRawAttributes)) {

View File

@ -35,6 +35,23 @@ export class OptionsParser {
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) {
return this.model.associations[key] !== undefined;
}
@ -109,23 +126,6 @@ export class OptionsParser {
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) {
const appends = this.options?.appends || [];
const except = [];
@ -142,13 +142,13 @@ export class OptionsParser {
}; // out put all fields by default
if (this.collection.isParent()) {
this.inheritFromSubQuery(attributes.include);
OptionsParser.appendInheritInspectAttribute(attributes.include, this.collection);
}
if (this.options?.fields) {
attributes = [];
if (this.collection.isParent()) {
this.inheritFromSubQuery(attributes);
OptionsParser.appendInheritInspectAttribute(attributes, this.collection);
}
// 将fields拆分为 attributes 和 appends

View File

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

View File

@ -71,6 +71,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
model: this.targetModel,
rootAttributes: findOptions.attributes,
includeOption: findOptions.include,
db: this.db,
});
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,
includeOption: opts.include,
rootOrder: opts.order,
db: this.database,
});
await eagerLoadingTree.load(