2020-06-27 14:36:50 +02:00
|
|
|
import ModelQuery, {ModelQueryResult, WhereTest} from "./ModelQuery";
|
2020-07-24 12:13:28 +02:00
|
|
|
import Model from "./Model";
|
|
|
|
import ModelFactory from "./ModelFactory";
|
2020-06-27 14:36:50 +02:00
|
|
|
|
|
|
|
export default abstract class ModelRelation<S extends Model, O extends Model, R extends O | O[] | null> {
|
|
|
|
protected readonly model: S;
|
2020-07-24 12:13:28 +02:00
|
|
|
protected readonly foreignFactory: ModelFactory<O>;
|
2020-06-27 14:36:50 +02:00
|
|
|
protected readonly query: ModelQuery<O>;
|
|
|
|
protected cachedModels?: R;
|
|
|
|
|
2020-07-24 12:13:28 +02:00
|
|
|
protected constructor(model: S, foreignFactory: ModelFactory<O>) {
|
2020-06-27 14:36:50 +02:00
|
|
|
this.model = model;
|
2020-07-24 12:13:28 +02:00
|
|
|
this.foreignFactory = foreignFactory;
|
|
|
|
this.query = this.foreignFactory.select();
|
2020-06-27 14:36:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public abstract clone(): ModelRelation<S, O, R>;
|
|
|
|
|
|
|
|
public constraint(queryModifier: QueryModifier<O>): this {
|
|
|
|
queryModifier(this.query);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract getModelID(): any;
|
|
|
|
|
|
|
|
protected abstract async compute(query: ModelQuery<O>): Promise<R>;
|
|
|
|
|
|
|
|
public async get(): Promise<R> {
|
|
|
|
if (this.cachedModels === undefined) {
|
|
|
|
this.cachedModels = await this.compute(this.query);
|
|
|
|
}
|
|
|
|
return this.cachedModels;
|
|
|
|
}
|
|
|
|
|
2020-06-27 17:11:31 +02:00
|
|
|
public getOrFail(): R {
|
|
|
|
if (!this.cachedModels) throw new Error('Models were not fetched');
|
|
|
|
return this.cachedModels;
|
|
|
|
}
|
|
|
|
|
2020-06-27 14:36:50 +02:00
|
|
|
public abstract async eagerLoad(relations: ModelRelation<S, O, R>[]): Promise<ModelQueryResult<O>>;
|
|
|
|
|
|
|
|
public abstract async populate(models: ModelQueryResult<O>): Promise<void>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type QueryModifier<M extends Model> = (query: ModelQuery<M>) => ModelQuery<M>;
|
|
|
|
|
|
|
|
export class OneModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O | null> {
|
|
|
|
protected readonly dbProperties: RelationDatabaseProperties;
|
|
|
|
|
2020-07-24 12:13:28 +02:00
|
|
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) {
|
|
|
|
super(model, foreignFactory);
|
2020-06-27 14:36:50 +02:00
|
|
|
this.dbProperties = dbProperties;
|
|
|
|
}
|
|
|
|
|
|
|
|
public clone(): OneModelRelation<S, O> {
|
2020-07-24 12:13:28 +02:00
|
|
|
return new OneModelRelation(this.model, this.foreignFactory, this.dbProperties);
|
2020-06-27 14:36:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public getModelID() {
|
|
|
|
return this.model[this.dbProperties.localKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async compute(query: ModelQuery<O>): Promise<O | null> {
|
|
|
|
this.query.where(this.dbProperties.foreignKey, this.getModelID());
|
|
|
|
return await query.first();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async eagerLoad(relations: ModelRelation<S, O, O | null>[]): Promise<ModelQueryResult<O>> {
|
|
|
|
this.query.where(
|
|
|
|
this.dbProperties.foreignKey,
|
|
|
|
relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined),
|
|
|
|
WhereTest.IN
|
|
|
|
);
|
|
|
|
return await this.query.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async populate(models: ModelQueryResult<O>): Promise<void> {
|
|
|
|
this.cachedModels = models.filter(m => m[this.dbProperties.foreignKey] === this.getModelID())[0] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ManyModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
|
|
|
protected readonly dbProperties: RelationDatabaseProperties;
|
|
|
|
|
2020-07-24 12:13:28 +02:00
|
|
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) {
|
|
|
|
super(model, foreignFactory);
|
2020-06-27 14:36:50 +02:00
|
|
|
this.dbProperties = dbProperties;
|
|
|
|
}
|
|
|
|
|
|
|
|
public clone(): ManyModelRelation<S, O> {
|
2020-07-24 12:13:28 +02:00
|
|
|
return new ManyModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
2020-06-27 14:36:50 +02:00
|
|
|
}
|
|
|
|
|
2020-06-27 14:58:39 +02:00
|
|
|
public cloneReduceToOne(): OneModelRelation<S, O> {
|
2020-07-24 12:13:28 +02:00
|
|
|
return new OneModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
2020-06-27 14:58:39 +02:00
|
|
|
}
|
|
|
|
|
2020-06-27 14:36:50 +02:00
|
|
|
public getModelID(): any {
|
|
|
|
return this.model[this.dbProperties.localKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async compute(query: ModelQuery<O>): Promise<O[]> {
|
|
|
|
this.query.where(this.dbProperties.foreignKey, this.getModelID());
|
|
|
|
return await query.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> {
|
|
|
|
this.query.where(
|
|
|
|
this.dbProperties.foreignKey,
|
|
|
|
relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined),
|
|
|
|
WhereTest.IN
|
|
|
|
);
|
|
|
|
return await this.query.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async populate(models: ModelQueryResult<O>): Promise<void> {
|
|
|
|
this.cachedModels = models.filter(m => m[this.dbProperties.foreignKey] === this.getModelID);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class ManyThroughModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
|
|
|
protected readonly dbProperties: PivotRelationDatabaseProperties;
|
|
|
|
|
2020-07-24 12:13:28 +02:00
|
|
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: PivotRelationDatabaseProperties) {
|
|
|
|
super(model, foreignFactory);
|
2020-06-27 14:36:50 +02:00
|
|
|
this.dbProperties = dbProperties;
|
|
|
|
this.query
|
|
|
|
.leftJoin(`${this.dbProperties.pivotTable} as pivot`)
|
2020-07-24 12:13:28 +02:00
|
|
|
.on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignFactory.table}.${this.dbProperties.foreignKey}`);
|
2020-06-27 14:36:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public clone(): ManyThroughModelRelation<S, O> {
|
2020-07-24 12:13:28 +02:00
|
|
|
return new ManyThroughModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
2020-06-27 14:36:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public getModelID(): any {
|
|
|
|
return this.model[this.dbProperties.localKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async compute(query: ModelQuery<O>): Promise<O[]> {
|
|
|
|
this.query.where(`pivot.${this.dbProperties.localPivotKey}`, this.getModelID());
|
|
|
|
return await query.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> {
|
|
|
|
this.query.where(
|
|
|
|
`pivot.${this.dbProperties.localPivotKey}`,
|
|
|
|
relations.map(r => r.getModelID()),
|
|
|
|
WhereTest.IN);
|
|
|
|
this.query.pivot(`pivot.${this.dbProperties.localPivotKey}`, `pivot.${this.dbProperties.foreignPivotKey}`);
|
|
|
|
return await this.query.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async populate(models: ModelQueryResult<O>): Promise<void> {
|
|
|
|
const ids = models.pivot!
|
|
|
|
.filter(p => p[this.dbProperties.localPivotKey] === this.getModelID())
|
|
|
|
.map(p => p[this.dbProperties.foreignPivotKey]);
|
|
|
|
this.cachedModels = models.filter(m => ids.indexOf(m[this.dbProperties.foreignKey]) >= 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export type RelationDatabaseProperties = {
|
|
|
|
localKey: string;
|
|
|
|
foreignKey: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type PivotRelationDatabaseProperties = RelationDatabaseProperties & {
|
|
|
|
pivotTable: string;
|
|
|
|
localPivotKey: string;
|
|
|
|
foreignPivotKey: string;
|
|
|
|
};
|