import {Model} from './Model' import {AbstractResultIterable} from '../builder/result/AbstractResultIterable' import {Connection} from '../connection/Connection' import {ModelBuilder} from './ModelBuilder' import {Instantiable} from '../../di' import {QueryRow} from '../types' import {collect, Collection} from '../../util' import {getFieldsMeta} from './Field' /** * Implementation of the result iterable that returns query results as instances of the defined model. */ export class ModelResultIterable> extends AbstractResultIterable { constructor( public readonly builder: ModelBuilder, public readonly connection: Connection, /** The model that should be instantiated for each row. */ protected readonly ModelClass: Instantiable, ) { super(builder, connection) } public get selectSQL(): string { return this.connection.dialect().renderSelect(this.builder) } async at(i: number): Promise { const query = this.connection.dialect().renderRangedSelect(this.selectSQL, i, i + 1) const row = (await this.connection.query(query)).rows.first() if ( row ) { const inflated = await this.inflateRow(row) await this.processEagerLoads(collect([inflated])) return inflated } } async range(start: number, end: number): Promise> { const query = this.connection.dialect().renderRangedSelect(this.selectSQL, start, end) const inflated = await (await this.connection.query(query)).rows.promiseMap(row => this.inflateRow(row)) await this.processEagerLoads(inflated) return inflated } async count(): Promise { const query = this.connection.dialect().renderCount(this.selectSQL) const result = (await this.connection.query(query)).rows.first() return result?.extollo_render_count ?? 0 } async all(): Promise> { const result = await this.connection.query(this.selectSQL) const inflated = await result.rows.promiseMap(row => this.inflateRow(row)) await this.processEagerLoads(inflated) return inflated } /** * Given a query row, create an instance of the configured model class from it. * @param row * @protected */ protected async inflateRow(row: QueryRow): Promise { const model = this.make(this.ModelClass) const fields = getFieldsMeta(model) return model.assumeFromSource( this.connection.normalizeRow(row, fields), ) } /** * Eager-load eager-loaded relations for the models in the query result. * @param results * @protected */ protected async processEagerLoads(results: Collection): Promise { if ( results.isEmpty() ) { // Nothing to load relations for, so no reason to perform more queries return } const eagers = this.builder.getEagerLoadedRelations() const model = this.make(this.ModelClass) for ( const name of eagers ) { // TODO support nested eager loads? const relation = model.getRelation(name) const select = relation.buildEagerQuery(this.builder, results) const resultCount = await select.get().count() const allRelated = resultCount ? await select.get().collect() : collect() results.each(result => { const resultRelation = result.getRelation(name as any) const resultRelated = resultRelation.matchResults(allRelated as any) resultRelation.setValue(resultRelated as any) }) } } clone(): ModelResultIterable { return new ModelResultIterable(this.builder, this.connection, this.ModelClass) } }