ModelRelation: sort recursive relations by tree

This commit is contained in:
Alice Gaudon 2020-09-08 18:12:39 +02:00
parent 892b830dc4
commit 969ab18b96
4 changed files with 29 additions and 11 deletions

View File

@ -1,6 +1,6 @@
{
"name": "wms-core",
"version": "0.22.0-rc.8",
"version": "0.22.0-rc.11",
"description": "Node web application framework and toolbelt.",
"repository": "https://gitlab.com/ArisuOngaku/wms-core",
"author": "Alice Gaudon <alice@gaudon.pro>",

View File

@ -45,6 +45,7 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
private _pivot?: string[];
private _union?: ModelQueryUnion;
private _recursiveRelation?: RelationDatabaseProperties;
private _reverseRecursiveRelation?: boolean;
private constructor(type: QueryType, factory: ModelFactory<M>, fields?: SelectFields) {
this.type = type;
@ -109,7 +110,7 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
public with(...relations: string[]): this {
relations.forEach(relation => {
const parts = relation.split('.');
this.relations.push(parts[0]);
if (this.relations.indexOf(parts[0]) < 0) this.relations.push(parts[0]);
if (parts.length > 1) {
if (!this.subRelations[parts[0]]) this.subRelations[parts[0]] = [];
this.subRelations[parts[0]].push(parts.slice(1).join('.'));
@ -135,9 +136,10 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
return this;
}
public recursive(relation: RelationDatabaseProperties): this {
public recursive(relation: RelationDatabaseProperties, reverse: boolean): this {
if (this.type !== QueryType.SELECT) throw new Error('Recursive queries are only implemented with SELECT.');
this._recursiveRelation = relation;
this._reverseRecursiveRelation = reverse;
return this;
}
@ -190,11 +192,14 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
case QueryType.SELECT:
if (this._recursiveRelation) {
const cteFields = fields.replace(RegExp(`${table}`, 'g'), 'o');
const idKey = this._reverseRecursiveRelation ? this._recursiveRelation.foreignKey : this._recursiveRelation.localKey;
const sortOrder = this._reverseRecursiveRelation ? 'DESC' : 'ASC';
query = `WITH RECURSIVE cte AS (`
+ `SELECT ${fields} FROM ${table}${where}`
+ `SELECT ${fields},1 AS __depth, CONCAT(\`${idKey}\`) AS __path FROM ${table}${where}`
+ ` UNION `
+ `SELECT ${cteFields} FROM ${table} AS o, cte AS c WHERE o.\`${this._recursiveRelation.foreignKey}\`=c.\`${this._recursiveRelation.localKey}\``
+ `) SELECT * FROM cte${join}${orderBy}${limit}`;
+ `SELECT ${cteFields},c.__depth + 1,CONCAT(c.__path,'/',o.\`${idKey}\`) AS __path FROM ${table} AS o, cte AS c WHERE o.\`${this._recursiveRelation.foreignKey}\`=c.\`${this._recursiveRelation.localKey}\``
+ `) SELECT * FROM cte${join}${orderBy || ` ORDER BY __path ${sortOrder}`}${limit}`;
} else {
query = `SELECT ${fields} FROM ${table}${join}${where}${orderBy}${limit}`;
}

View File

@ -201,9 +201,12 @@ export class ManyThroughModelRelation<S extends Model, O extends Model> extends
}
export class RecursiveModelRelation<M extends Model> extends ManyModelRelation<M, M> {
public constructor(model: M, foreignModelType: ModelType<M>, dbProperties: RelationDatabaseProperties) {
private readonly reverse: boolean;
public constructor(model: M, foreignModelType: ModelType<M>, dbProperties: RelationDatabaseProperties, reverse: boolean = false) {
super(model, foreignModelType, dbProperties);
this.constraint(query => query.recursive(this.dbProperties));
this.constraint(query => query.recursive(this.dbProperties, reverse));
this.reverse = reverse;
}
public clone(): RecursiveModelRelation<M> {
@ -221,6 +224,8 @@ export class RecursiveModelRelation<M extends Model> extends ManyModelRelation<M
this.cachedModels!.find(cached => cached[this.dbProperties.localKey] === model[this.dbProperties.foreignKey])
));
} while (count !== this.cachedModels.length);
if (this.reverse) this.cachedModels!.reverse();
}
}

View File

@ -59,11 +59,19 @@ describe('Test ModelQuery', () => {
const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, '*');
query.where('f1', 'v1');
query.leftJoin('test').on('model.j1', 'test.j2');
query.recursive({localKey: 'local', foreignKey: 'foreign'});
query.sortBy('f2', 'ASC').limit(8);
query.recursive({localKey: 'local', foreignKey: 'foreign'}, false);
query.limit(8);
expect(query.toString(true)).toBe("WITH RECURSIVE cte AS (SELECT `model`.* FROM `model` WHERE `f1`=? UNION SELECT o.* FROM `model` AS o, cte AS c WHERE o.`foreign`=c.`local`) SELECT * FROM cte LEFT JOIN `test` ON `model`.`j1`=`test`.`j2` ORDER BY `f2` ASC LIMIT 8");
expect(query.toString(true)).toBe("WITH RECURSIVE cte AS (SELECT `model`.*,1 AS __depth, CONCAT(`local`) AS __path FROM `model` WHERE `f1`=? UNION SELECT o.*,c.__depth + 1,CONCAT(c.__path,'/',o.`local`) AS __path FROM `model` AS o, cte AS c WHERE o.`foreign`=c.`local`) SELECT * FROM cte LEFT JOIN `test` ON `model`.`j1`=`test`.`j2` ORDER BY __path ASC LIMIT 8");
expect(query.variables).toStrictEqual(['v1']);
const reversedQuery = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, '*');
reversedQuery.where('f1', 'v1');
reversedQuery.leftJoin('test').on('model.j1', 'test.j2');
reversedQuery.recursive({localKey: 'local', foreignKey: 'foreign'}, true);
expect(reversedQuery.toString(true)).toBe("WITH RECURSIVE cte AS (SELECT `model`.*,1 AS __depth, CONCAT(`foreign`) AS __path FROM `model` WHERE `f1`=? UNION SELECT o.*,c.__depth + 1,CONCAT(c.__path,'/',o.`foreign`) AS __path FROM `model` AS o, cte AS c WHERE o.`foreign`=c.`local`) SELECT * FROM cte LEFT JOIN `test` ON `model`.`j1`=`test`.`j2` ORDER BY __path DESC");
expect(reversedQuery.variables).toStrictEqual(['v1']);
});
test('union queries', () => {