You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/orm/model/ModelResultIterable.ts

105 lines
3.8 KiB

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<T extends Model<T>> extends AbstractResultIterable<T> {
constructor(
public readonly builder: ModelBuilder<T>,
public readonly connection: Connection,
/** The model that should be instantiated for each row. */
protected readonly ModelClass: Instantiable<T>,
) {
super(builder, connection)
}
public get selectSQL(): string {
return this.connection.dialect().renderSelect(this.builder)
}
async at(i: number): Promise<T | undefined> {
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<Collection<T>> {
const query = this.connection.dialect().renderRangedSelect(this.selectSQL, start, end)
const inflated = await (await this.connection.query(query)).rows.promiseMap<T>(row => this.inflateRow(row))
await this.processEagerLoads(inflated)
return inflated
}
async count(): Promise<number> {
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<Collection<T>> {
const result = await this.connection.query(this.selectSQL)
const inflated = await result.rows.promiseMap<T>(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<T> {
const model = this.make<T>(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<T>): Promise<void> {
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<T>(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<T> {
return new ModelResultIterable(this.builder, this.connection, this.ModelClass)
}
}