import { OrderDirection , QueryBuilder } from "./querybuilder.ts" ; import type { Adapter , DatabaseResult , DatabaseValues , } from "./adapters/adapter.ts" ; import { createModel , createModels , findColumn , getColumns , getPrimaryKeyInfo , getRelations , getTableName , mapQueryResult , mapSingleQueryResult , mapValueProperties , } from "./utils/models.ts" ; import { RelationType } from "./model.ts" ; import { quote } from "./utils/dialect.ts" ; import type { DeepPartial } from "./manager.ts" ; import { Q } from "./q.ts" ; /**
 * Perform query to a model.
 */ export class ModelQuery < T > { /** The query builder to construct the query */ private builder : QueryBuilder ; /** The table to fetch */ private tableName : string ; /** Included relationships */ private includes : string [ ] = [ ] ; /**
     * Create a new model query.
     */ constructor ( private modelClass , private adapter ) { this . tableName = getTableName ( modelClass ) ; this . builder = new QueryBuilder ( this . tableName , adapter ) ; } param column param value public where ( column , value ) ; param column param expresion public where ( column , expression ) ; /** Add basic WHERE clause to query */ public where ( column , expression ) { this . builder . where ( this . getColumnName ( column ) , expression as DatabaseValues ) ; return this ; } param column param value public not ( column , value ) ; param column param expresion public not ( column , expression ) ; /** Add WHERE NOT clause to query. */ public not ( column , expression ) { this . builder . not ( this . getColumnName ( column ) , expression as DatabaseValues ) ; return this ; } param column param value public or ( column , value ) ; param column param expresion public or ( column , expression ) ; /** Add WHERE ... OR clause to query. */ public or ( column , expression ) { this . builder . or ( this . getColumnName ( column ) , expression as DatabaseValues ) ; return this ; } param limit public limit ( limit ) { this . builder . limit ( limit ) ; return this ; } param offset public offset ( offset ) { this . builder . offset ( offset ) ; return this ; } param column param direction public order ( column , direction ? ) { this . builder . order ( this . getColumnName ( column as string ) , direction ) ; return this ; } param columns public include ( ... columns ) { for ( const relation of getRelations ( this . modelClass , columns ) ) { const relationTableName = getTableName ( relation . getModel ( ) ) ; const primaryKeyInfo = getPrimaryKeyInfo ( this . modelClass ) ; const relationPrimaryKeyInfo = getPrimaryKeyInfo ( relation . getModel ( ) ) ; if ( relation . type === RelationType . HasMany ) { this . builder . leftJoin ( relationTableName , relationTableName + "." + relation . targetColumn , this . tableName + "." + primaryKeyInfo . name ) ; } else if ( relation . type === RelationType . BelongsTo ) { this . builder . leftJoin ( relationTableName , relationTableName + "." + relationPrimaryKeyInfo . name , this . tableName + "." + relation . targetColumn ) ; } this . selectModelColumns ( relation . getModel ( ) ) ; this . includes . push ( relation . propertyKey ) ; } return this ; } /**
     * Count models with given conditions.
     */ public async count ( ) { const primaryKeyInfo = getPrimaryKeyInfo ( this . modelClass ) ; const result = await this . builder . countDistinct ( primaryKeyInfo . name , "count" ) . execute ( ) ; return ( Number ( result [ 0 ] . count ) as number ) || 0 ; } param data public async update ( data ) { const values = mapValueProperties ( this . modelClass , data as any , "name" ) ; await this . builder . update ( values ) . execute ( ) ; } /**
     * Delete models with given conditions.
     */ public async delete ( ) { await this . builder . delete ( ) . execute ( ) ; } /**
     * Find a single record that match given conditions. If multiple
     * found, it will return the first one.
     */ public async first ( ) { this . selectModelColumns ( this . modelClass ) ; const isIncludingHasMany = getRelations ( this . modelClass , this . includes ) . find ( ( item ) => item . type === RelationType . HasMany ) ; let result ; if ( isIncludingHasMany ) { const primaryKeyInfo = getPrimaryKeyInfo ( this . modelClass ) ; const tableName = getTableName ( this . modelClass ) ; const alias = quote ( "distinctAlias" , this . adapter . dialect ) ; const primaryColumn = quote ( tableName + "__" + primaryKeyInfo . name , this . adapter . dialect ) ; const { text , values } = this . builder . toSQL ( ) ; const queryString = `SELECT DISTINCT ${ alias }.${ primaryColumn } FROM (${ text . slice ( 0 , text . length - 1 ) }) ${ alias } LIMIT 1;` ; const recordIds = await this . adapter . query ( queryString , values ) ; if ( recordIds . length === 1 ) { const id = recordIds [ 0 ] [ tableName + "__" + primaryKeyInfo . name ] ; result = await this . builder . where ( primaryKeyInfo . name , Q . eq ( id ) ) . execute ( ) ; } else { return null ; } } else { result = await this . builder . first ( ) . execute ( ) ; } if ( result . length >= 1 ) { const record = this . includes . length >= 1 ? mapQueryResult ( this . modelClass , result , this . includes . length >= 1 ? this . includes : undefined ) [ 0 ] : mapSingleQueryResult ( this . modelClass , result [ 0 ] ) ; return createModel ( this . modelClass , record , true ) ; } else { return null ; } } /**
     * Find records that match given conditions.
     */ public async all ( ) { this . selectModelColumns ( this . modelClass ) ; const result = await this . builder . execute ( ) ; return createModels ( this . modelClass , mapQueryResult ( this . modelClass , result , this . includes . length >= 1 ? this . includes : undefined ) , true ) ; } param modelClass private selectModelColumns ( modelClass ) { const tableName = getTableName ( modelClass ) ; const columns = getColumns ( modelClass ) . map ( ( column ) : [ string , string ] => [ tableName + "." + column . name , tableName + "__" + column . name , ] ) ; this . builder . select ( ... columns ) ; } param propertyKey private getColumnName ( propertyKey ) { const column = findColumn ( this . modelClass , propertyKey ) ; if ( ! column ) { throw new Error ( `Column '${ propertyKey }' doesn't exist in model '${ this . modelClass . name }'!` ) ; } return column . name ; } }