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:
parent
bc00bd161a
commit
57d47371da
@ -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);
|
||||||
|
@ -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',
|
||||||
|
@ -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)) {
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user