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", "name": "wms-core",
"version": "0.22.0-rc.8", "version": "0.22.0-rc.11",
"description": "Node web application framework and toolbelt.", "description": "Node web application framework and toolbelt.",
"repository": "https://gitlab.com/ArisuOngaku/wms-core", "repository": "https://gitlab.com/ArisuOngaku/wms-core",
"author": "Alice Gaudon <alice@gaudon.pro>", "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 _pivot?: string[];
private _union?: ModelQueryUnion; private _union?: ModelQueryUnion;
private _recursiveRelation?: RelationDatabaseProperties; private _recursiveRelation?: RelationDatabaseProperties;
private _reverseRecursiveRelation?: boolean;
private constructor(type: QueryType, factory: ModelFactory<M>, fields?: SelectFields) { private constructor(type: QueryType, factory: ModelFactory<M>, fields?: SelectFields) {
this.type = type; this.type = type;
@ -109,7 +110,7 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
public with(...relations: string[]): this { public with(...relations: string[]): this {
relations.forEach(relation => { relations.forEach(relation => {
const parts = relation.split('.'); 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 (parts.length > 1) {
if (!this.subRelations[parts[0]]) this.subRelations[parts[0]] = []; if (!this.subRelations[parts[0]]) this.subRelations[parts[0]] = [];
this.subRelations[parts[0]].push(parts.slice(1).join('.')); 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; 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.'); if (this.type !== QueryType.SELECT) throw new Error('Recursive queries are only implemented with SELECT.');
this._recursiveRelation = relation; this._recursiveRelation = relation;
this._reverseRecursiveRelation = reverse;
return this; return this;
} }
@ -190,11 +192,14 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
case QueryType.SELECT: case QueryType.SELECT:
if (this._recursiveRelation) { if (this._recursiveRelation) {
const cteFields = fields.replace(RegExp(`${table}`, 'g'), 'o'); 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 (` query = `WITH RECURSIVE cte AS (`
+ `SELECT ${fields} FROM ${table}${where}` + `SELECT ${fields},1 AS __depth, CONCAT(\`${idKey}\`) AS __path FROM ${table}${where}`
+ ` UNION ` + ` UNION `
+ `SELECT ${cteFields} FROM ${table} AS o, cte AS c WHERE o.\`${this._recursiveRelation.foreignKey}\`=c.\`${this._recursiveRelation.localKey}\`` + `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}${limit}`; + `) SELECT * FROM cte${join}${orderBy || ` ORDER BY __path ${sortOrder}`}${limit}`;
} else { } else {
query = `SELECT ${fields} FROM ${table}${join}${where}${orderBy}${limit}`; 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> { 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); 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> { 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]) this.cachedModels!.find(cached => cached[this.dbProperties.localKey] === model[this.dbProperties.foreignKey])
)); ));
} while (count !== this.cachedModels.length); } 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>, '*'); const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, '*');
query.where('f1', 'v1'); query.where('f1', 'v1');
query.leftJoin('test').on('model.j1', 'test.j2'); query.leftJoin('test').on('model.j1', 'test.j2');
query.recursive({localKey: 'local', foreignKey: 'foreign'}); query.recursive({localKey: 'local', foreignKey: 'foreign'}, false);
query.sortBy('f2', 'ASC').limit(8); 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']); 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', () => { test('union queries', () => {