diff --git a/packages/entity-database-adapter-knex/src/AuthorizationResultBasedKnexEntityLoader.ts b/packages/entity-database-adapter-knex/src/AuthorizationResultBasedKnexEntityLoader.ts index 0084313c0..6e0bfe60d 100644 --- a/packages/entity-database-adapter-knex/src/AuthorizationResultBasedKnexEntityLoader.ts +++ b/packages/entity-database-adapter-knex/src/AuthorizationResultBasedKnexEntityLoader.ts @@ -11,14 +11,70 @@ import { Result } from '@expo/results'; import { FieldEqualityCondition, isSingleValueFieldEqualityCondition, - QuerySelectionModifiers, - QuerySelectionModifiersWithOrderByFragment, - QuerySelectionModifiersWithOrderByRaw, + OrderByOrdering, } from './BasePostgresEntityDatabaseAdapter'; import { BaseSQLQueryBuilder } from './BaseSQLQueryBuilder'; import { SQLFragment } from './SQLOperator'; import { EntityKnexDataManager } from './internal/EntityKnexDataManager'; +export interface EntityLoaderOrderByClause< + TFields extends Record, + TSelectedFields extends keyof TFields, +> { + /** + * The field name to order by. + */ + fieldName: TSelectedFields; + + /** + * The OrderByOrdering to order by. + */ + order: OrderByOrdering; +} + +/** + * SQL modifiers that only affect the selection but not the projection. + */ +export interface EntityLoaderQuerySelectionModifiers< + TFields extends Record, + TSelectedFields extends keyof TFields, +> { + /** + * Order the entities by specified columns and orders. + */ + orderBy?: readonly EntityLoaderOrderByClause[]; + + /** + * Skip the specified number of entities queried before returning. + */ + offset?: number; + + /** + * Limit the number of entities returned. + */ + limit?: number; +} + +export interface EntityLoaderQuerySelectionModifiersWithOrderByRaw< + TFields extends Record, + TSelectedFields extends keyof TFields, +> extends EntityLoaderQuerySelectionModifiers { + /** + * Order the entities by a raw SQL `ORDER BY` clause. + */ + orderByRaw?: string; +} + +export interface EntityLoaderQuerySelectionModifiersWithOrderByFragment< + TFields extends Record, + TSelectedFields extends keyof TFields, +> extends EntityLoaderQuerySelectionModifiers { + /** + * Order the entities by a SQL fragment `ORDER BY` clause. + */ + orderByFragment?: SQLFragment; +} + /** * Authorization-result-based knex entity loader for non-data-loader-based load methods. * All loads through this loader are results (or null for some loader methods), where an @@ -60,8 +116,11 @@ export class AuthorizationResultBasedKnexEntityLoader< */ async loadFirstByFieldEqualityConjunctionAsync>( fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: Omit, 'limit'> & - Required, 'orderBy'>>, + querySelectionModifiers: Omit< + EntityLoaderQuerySelectionModifiers, + 'limit' + > & + Required, 'orderBy'>>, ): Promise | null> { const results = await this.loadManyByFieldEqualityConjunctionAsync(fieldEqualityOperands, { ...querySelectionModifiers, @@ -76,7 +135,7 @@ export class AuthorizationResultBasedKnexEntityLoader< */ async loadManyByFieldEqualityConjunctionAsync>( fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: QuerySelectionModifiers = {}, + querySelectionModifiers: EntityLoaderQuerySelectionModifiers = {}, ): Promise[]> { for (const fieldEqualityOperand of fieldEqualityOperands) { const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand) @@ -101,7 +160,10 @@ export class AuthorizationResultBasedKnexEntityLoader< async loadManyByRawWhereClauseAsync( rawWhereClause: string, bindings: any[] | object, - querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw = {}, + querySelectionModifiers: EntityLoaderQuerySelectionModifiersWithOrderByRaw< + TFields, + TSelectedFields + > = {}, ): Promise[]> { const fieldObjects = await this.knexDataManager.loadManyByRawWhereClauseAsync( this.queryContext, @@ -118,7 +180,10 @@ export class AuthorizationResultBasedKnexEntityLoader< */ loadManyBySQL( fragment: SQLFragment, - modifiers: QuerySelectionModifiersWithOrderByFragment = {}, + modifiers: EntityLoaderQuerySelectionModifiersWithOrderByFragment< + TFields, + TSelectedFields + > = {}, ): AuthorizationResultBasedSQLQueryBuilder< TFields, TIDField, @@ -153,7 +218,7 @@ export class AuthorizationResultBasedSQLQueryBuilder< TSelectedFields >, TSelectedFields extends keyof TFields, -> extends BaseSQLQueryBuilder> { +> extends BaseSQLQueryBuilder> { constructor( private readonly knexDataManager: EntityKnexDataManager, private readonly constructionUtils: EntityConstructionUtils< @@ -166,7 +231,7 @@ export class AuthorizationResultBasedSQLQueryBuilder< >, private readonly queryContext: EntityQueryContext, sqlFragment: SQLFragment, - modifiers: QuerySelectionModifiersWithOrderByFragment, + modifiers: EntityLoaderQuerySelectionModifiersWithOrderByFragment, ) { super(sqlFragment, modifiers); } diff --git a/packages/entity-database-adapter-knex/src/BasePostgresEntityDatabaseAdapter.ts b/packages/entity-database-adapter-knex/src/BasePostgresEntityDatabaseAdapter.ts index 294d711db..b7df2f655 100644 --- a/packages/entity-database-adapter-knex/src/BasePostgresEntityDatabaseAdapter.ts +++ b/packages/entity-database-adapter-knex/src/BasePostgresEntityDatabaseAdapter.ts @@ -75,24 +75,26 @@ export enum OrderByOrdering { DESCENDING = 'desc', } +export interface PostgresOrderByClause> { + /** + * The field name to order by. + */ + fieldName: keyof TFields; + + /** + * The OrderByOrdering to order by. + */ + order: OrderByOrdering; +} + /** * SQL modifiers that only affect the selection but not the projection. */ -export interface QuerySelectionModifiers> { +export interface PostgresQuerySelectionModifiers> { /** * Order the entities by specified columns and orders. */ - orderBy?: { - /** - * The field name to order by. - */ - fieldName: keyof TFields; - - /** - * The OrderByOrdering to order by. - */ - order: OrderByOrdering; - }[]; + orderBy?: readonly PostgresOrderByClause[]; /** * Skip the specified number of entities queried before returning. @@ -105,18 +107,18 @@ export interface QuerySelectionModifiers> { limit?: number; } -export interface QuerySelectionModifiersWithOrderByRaw< +export interface PostgresQuerySelectionModifiersWithOrderByRaw< TFields extends Record, -> extends QuerySelectionModifiers { +> extends PostgresQuerySelectionModifiers { /** * Order the entities by a raw SQL `ORDER BY` clause. */ orderByRaw?: string; } -export interface QuerySelectionModifiersWithOrderByFragment< +export interface PostgresQuerySelectionModifiersWithOrderByFragment< TFields extends Record, -> extends QuerySelectionModifiers { +> extends PostgresQuerySelectionModifiers { /** * Order the entities by a SQL fragment `ORDER BY` clause. */ @@ -159,7 +161,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< async fetchManyByFieldEqualityConjunctionAsync( queryContext: EntityQueryContext, fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: QuerySelectionModifiers, + querySelectionModifiers: PostgresQuerySelectionModifiers, ): Promise[]> { const tableFieldSingleValueOperands: TableFieldSingleValueEqualityCondition[] = []; const tableFieldMultipleValueOperands: TableFieldMultiValueEqualityCondition[] = []; @@ -211,7 +213,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< queryContext: EntityQueryContext, rawWhereClause: string, bindings: any[] | object, - querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByRaw, ): Promise[]> { const results = await this.fetchManyByRawWhereClauseInternalAsync( queryContext.getQueryInterface(), @@ -245,7 +247,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< async fetchManyBySQLFragmentAsync( queryContext: EntityQueryContext, sqlFragment: SQLFragment, - querySelectionModifiers: QuerySelectionModifiersWithOrderByFragment, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByFragment, ): Promise[]> { const results = await this.fetchManyBySQLFragmentInternalAsync( queryContext.getQueryInterface(), @@ -267,7 +269,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< ): Promise; private convertToTableQueryModifiersWithOrderByRaw( - querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByRaw, ): TableQuerySelectionModifiersWithOrderByRaw { return { ...this.convertToTableQueryModifiers(querySelectionModifiers), @@ -276,7 +278,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< } private convertToTableQueryModifiersWithOrderByFragment( - querySelectionModifiers: QuerySelectionModifiersWithOrderByFragment, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByFragment, ): TableQuerySelectionModifiersWithOrderByFragment { return { ...this.convertToTableQueryModifiers(querySelectionModifiers), @@ -285,7 +287,7 @@ export abstract class BasePostgresEntityDatabaseAdapter< } private convertToTableQueryModifiers( - querySelectionModifiers: QuerySelectionModifiers, + querySelectionModifiers: PostgresQuerySelectionModifiers, ): TableQuerySelectionModifiers { const orderBy = querySelectionModifiers.orderBy; return { diff --git a/packages/entity-database-adapter-knex/src/BaseSQLQueryBuilder.ts b/packages/entity-database-adapter-knex/src/BaseSQLQueryBuilder.ts index 6830f8feb..bf43d3cf3 100644 --- a/packages/entity-database-adapter-knex/src/BaseSQLQueryBuilder.ts +++ b/packages/entity-database-adapter-knex/src/BaseSQLQueryBuilder.ts @@ -1,13 +1,18 @@ import { - OrderByOrdering, - QuerySelectionModifiersWithOrderByFragment, -} from './BasePostgresEntityDatabaseAdapter'; + EntityLoaderOrderByClause, + EntityLoaderQuerySelectionModifiersWithOrderByFragment, +} from './AuthorizationResultBasedKnexEntityLoader'; +import { OrderByOrdering } from './BasePostgresEntityDatabaseAdapter'; import { SQLFragment } from './SQLOperator'; /** * Base SQL query builder that provides common functionality for building SQL queries. */ -export abstract class BaseSQLQueryBuilder, TResultType> { +export abstract class BaseSQLQueryBuilder< + TFields extends Record, + TSelectedFields extends keyof TFields, + TResultType, +> { private executed = false; constructor( @@ -15,7 +20,7 @@ export abstract class BaseSQLQueryBuilder, T private readonly modifiers: { limit?: number; offset?: number; - orderBy?: { fieldName: keyof TFields; order: OrderByOrdering }[]; + orderBy?: readonly EntityLoaderOrderByClause[]; orderByFragment?: SQLFragment; }, ) {} @@ -39,7 +44,7 @@ export abstract class BaseSQLQueryBuilder, T /** * Order by a field. Can be called multiple times to add multiple order bys. */ - orderBy(fieldName: keyof TFields, order: OrderByOrdering = OrderByOrdering.ASCENDING): this { + orderBy(fieldName: TSelectedFields, order: OrderByOrdering = OrderByOrdering.ASCENDING): this { this.modifiers.orderBy = [...(this.modifiers.orderBy ?? []), { fieldName, order }]; return this; } @@ -71,7 +76,10 @@ export abstract class BaseSQLQueryBuilder, T /** * Get the current modifiers as QuerySelectionModifiersWithOrderByFragment */ - protected getModifiers(): QuerySelectionModifiersWithOrderByFragment { + protected getModifiers(): EntityLoaderQuerySelectionModifiersWithOrderByFragment< + TFields, + TSelectedFields + > { return this.modifiers; } diff --git a/packages/entity-database-adapter-knex/src/EnforcingKnexEntityLoader.ts b/packages/entity-database-adapter-knex/src/EnforcingKnexEntityLoader.ts index 7005eb312..eb91ed17c 100644 --- a/packages/entity-database-adapter-knex/src/EnforcingKnexEntityLoader.ts +++ b/packages/entity-database-adapter-knex/src/EnforcingKnexEntityLoader.ts @@ -1,12 +1,12 @@ import { EntityPrivacyPolicy, ReadonlyEntity, ViewerContext } from '@expo/entity'; -import { AuthorizationResultBasedKnexEntityLoader } from './AuthorizationResultBasedKnexEntityLoader'; import { - FieldEqualityCondition, - QuerySelectionModifiers, - QuerySelectionModifiersWithOrderByFragment, - QuerySelectionModifiersWithOrderByRaw, -} from './BasePostgresEntityDatabaseAdapter'; + AuthorizationResultBasedKnexEntityLoader, + EntityLoaderQuerySelectionModifiers, + EntityLoaderQuerySelectionModifiersWithOrderByFragment, + EntityLoaderQuerySelectionModifiersWithOrderByRaw, +} from './AuthorizationResultBasedKnexEntityLoader'; +import { FieldEqualityCondition } from './BasePostgresEntityDatabaseAdapter'; import { BaseSQLQueryBuilder } from './BaseSQLQueryBuilder'; import { SQLFragment } from './SQLOperator'; @@ -52,8 +52,11 @@ export class EnforcingKnexEntityLoader< */ async loadFirstByFieldEqualityConjunctionAsync>( fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: Omit, 'limit'> & - Required, 'orderBy'>>, + querySelectionModifiers: Omit< + EntityLoaderQuerySelectionModifiers, + 'limit' + > & + Required, 'orderBy'>>, ): Promise { const entityResult = await this.knexEntityLoader.loadFirstByFieldEqualityConjunctionAsync( fieldEqualityOperands, @@ -74,7 +77,7 @@ export class EnforcingKnexEntityLoader< */ async loadManyByFieldEqualityConjunctionAsync>( fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: QuerySelectionModifiers = {}, + querySelectionModifiers: EntityLoaderQuerySelectionModifiers = {}, ): Promise { const entityResults = await this.knexEntityLoader.loadManyByFieldEqualityConjunctionAsync( fieldEqualityOperands, @@ -116,7 +119,10 @@ export class EnforcingKnexEntityLoader< async loadManyByRawWhereClauseAsync( rawWhereClause: string, bindings: any[] | object, - querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw = {}, + querySelectionModifiers: EntityLoaderQuerySelectionModifiersWithOrderByRaw< + TFields, + TSelectedFields + > = {}, ): Promise { const entityResults = await this.knexEntityLoader.loadManyByRawWhereClauseAsync( rawWhereClause, @@ -147,7 +153,10 @@ export class EnforcingKnexEntityLoader< */ loadManyBySQL( fragment: SQLFragment, - modifiers: QuerySelectionModifiersWithOrderByFragment = {}, + modifiers: EntityLoaderQuerySelectionModifiersWithOrderByFragment< + TFields, + TSelectedFields + > = {}, ): EnforcingSQLQueryBuilder< TFields, TIDField, @@ -177,7 +186,7 @@ export class EnforcingSQLQueryBuilder< TSelectedFields >, TSelectedFields extends keyof TFields, -> extends BaseSQLQueryBuilder { +> extends BaseSQLQueryBuilder { constructor( private readonly knexEntityLoader: AuthorizationResultBasedKnexEntityLoader< TFields, @@ -188,7 +197,7 @@ export class EnforcingSQLQueryBuilder< TSelectedFields >, sqlFragment: SQLFragment, - modifiers: QuerySelectionModifiersWithOrderByFragment, + modifiers: EntityLoaderQuerySelectionModifiersWithOrderByFragment, ) { super(sqlFragment, modifiers); } diff --git a/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts b/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts index 21cea0c27..ca73356aa 100644 --- a/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts +++ b/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts @@ -8,9 +8,9 @@ import { import { BasePostgresEntityDatabaseAdapter, FieldEqualityCondition, - QuerySelectionModifiers, - QuerySelectionModifiersWithOrderByFragment, - QuerySelectionModifiersWithOrderByRaw, + PostgresQuerySelectionModifiers, + PostgresQuerySelectionModifiersWithOrderByFragment, + PostgresQuerySelectionModifiersWithOrderByRaw, } from '../BasePostgresEntityDatabaseAdapter'; import { SQLFragment } from '../SQLOperator'; @@ -42,7 +42,7 @@ export class EntityKnexDataManager< async loadManyByFieldEqualityConjunctionAsync( queryContext: EntityQueryContext, fieldEqualityOperands: FieldEqualityCondition[], - querySelectionModifiers: QuerySelectionModifiers, + querySelectionModifiers: PostgresQuerySelectionModifiers, ): Promise[]> { return await timeAndLogLoadEventAsync( this.metricsAdapter, @@ -71,7 +71,7 @@ export class EntityKnexDataManager< queryContext: EntityQueryContext, rawWhereClause: string, bindings: any[] | object, - querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByRaw, ): Promise[]> { return await timeAndLogLoadEventAsync( this.metricsAdapter, @@ -91,7 +91,7 @@ export class EntityKnexDataManager< async loadManyBySQLFragmentAsync( queryContext: EntityQueryContext, sqlFragment: SQLFragment, - querySelectionModifiers: QuerySelectionModifiersWithOrderByFragment, + querySelectionModifiers: PostgresQuerySelectionModifiersWithOrderByFragment, ): Promise[]> { return await timeAndLogLoadEventAsync( this.metricsAdapter,