Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/__tests__/sale.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'

@Entity({ name: 'sales' })
export class SaleEntity {
@PrimaryGeneratedColumn()
id: number

@Column({ name: 'item_name', nullable: false })
itemName: string

@Column({ nullable: false })
quantity: number

@Column({ nullable: false, type: 'decimal', precision: 10, scale: 2 })
unitPrice: number

@Column({ nullable: false, type: 'decimal', precision: 10, scale: 2 })
totalPrice: number
}
185 changes: 185 additions & 0 deletions src/paginate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
parseFilterToken,
} from './filter'
import { PaginateConfig, Paginated, PaginationLimit, paginate } from './paginate'
import { SaleEntity } from './__tests__/sale.entity'

const isoStringToDate = (isoString) => new Date(isoString)

Expand All @@ -33,6 +34,7 @@ describe('paginate', () => {
let toyShopAddressRepository: Repository<ToyShopAddressEntity>
let catHomeRepo: Repository<CatHomeEntity>
let catHomePillowRepo: Repository<CatHomePillowEntity>
let saleRepo: Repository<SaleEntity>
let cats: CatEntity[]
let catToys: CatToyEntity[]
let catToysWithoutShop: CatToyEntity[]
Expand All @@ -54,6 +56,7 @@ describe('paginate', () => {
CatHomeEntity,
CatHomePillowEntity,
ToyShopEntity,
SaleEntity,
process.env.DB === 'postgres' ? CatHairEntity : undefined,
],
}
Expand Down Expand Up @@ -98,6 +101,7 @@ describe('paginate', () => {
catHomePillowRepo = dataSource.getRepository(CatHomePillowEntity)
toyShopRepo = dataSource.getRepository(ToyShopEntity)
toyShopAddressRepository = dataSource.getRepository(ToyShopAddressEntity)
saleRepo = dataSource.getRepository(SaleEntity)

cats = await catRepo.save([
catRepo.create({
Expand Down Expand Up @@ -203,6 +207,99 @@ describe('paginate', () => {
catHairRepo.create({ name: 'none' }),
])
}

await saleRepo.save([
saleRepo.create({
itemName: 'Laptop',
quantity: 1,
unitPrice: 1200.0,
totalPrice: 1200.0,
}),
saleRepo.create({
itemName: 'Smartphone',
quantity: 2,
unitPrice: 600.0,
totalPrice: 1200.0,
}),
saleRepo.create({
itemName: 'Headphones',
quantity: 1,
unitPrice: 150.0,
totalPrice: 150.0,
}),
saleRepo.create({
itemName: 'Laptop',
quantity: 1,
unitPrice: 1200.0,
totalPrice: 1200.0,
}),
saleRepo.create({
itemName: 'Mouse',
quantity: 3,
unitPrice: 20.0,
totalPrice: 60.0,
}),
saleRepo.create({
itemName: 'Keyboard',
quantity: 2,
unitPrice: 80.0,
totalPrice: 160.0,
}),
saleRepo.create({
itemName: 'Monitor',
quantity: 1,
unitPrice: 300.0,
totalPrice: 300.0,
}),
saleRepo.create({
itemName: 'Laptop',
quantity: 1,
unitPrice: 1200.0,
totalPrice: 1200.0,
}),
saleRepo.create({
itemName: 'Printer',
quantity: 1,
unitPrice: 250.0,
totalPrice: 250.0,
}),
saleRepo.create({
itemName: 'Smartphone',
quantity: 1,
unitPrice: 600.0,
totalPrice: 600.0,
}),
saleRepo.create({
itemName: 'Monitor',
quantity: 2,
unitPrice: 300.0,
totalPrice: 600.0,
}),
saleRepo.create({
itemName: 'Headphones',
quantity: 1,
unitPrice: 150.0,
totalPrice: 150.0,
}),
saleRepo.create({
itemName: 'Keyboard',
quantity: 1,
unitPrice: 80.0,
totalPrice: 80.0,
}),
saleRepo.create({
itemName: 'Laptop Stand',
quantity: 2,
unitPrice: 50.0,
totalPrice: 100.0,
}),
saleRepo.create({
itemName: 'Smartphone',
quantity: 1,
unitPrice: 600.0,
totalPrice: 600.0,
}),
])
})

if (process.env.DB === 'postgres') {
Expand Down Expand Up @@ -3049,6 +3146,50 @@ describe('paginate', () => {
expect(result.links.current).toBe(`?page=1&limit=20&sortBy=id:ASC&search=brown`)
})
})

it('should return correct amount of data with complete correct columns in each data item when data queried using manual sql aggregates functions', async () => {
const salesQuery = saleRepo
.createQueryBuilder('sale')
.select([
'sale.itemName as itemName',
'SUM(sale.totalPrice) as totalSales',
'COUNT(*) as numberOfSales',
])
.groupBy('sale.itemName')

const config: PaginateConfig<SaleEntity> = {
sortableColumns: ['itemName'],
getDataAsRaw: true,
rowCountAsItIs: true,
}

const query: PaginateQuery = {
path: '',
limit: 3,
page: 1,
}

const result = await paginate<SaleEntity>(query, salesQuery, config)

type MyRow = {
itemname: string
totalsales: number
numberofsales: number
}

result.data.forEach((data) => {
const sale = data as unknown as MyRow
expect(sale.itemname).toBeDefined()
expect(sale.totalsales).toBeDefined()
expect(sale.numberofsales).toBeDefined()
})

expect(result.meta.totalItems).toEqual(8)
expect(result.meta.totalPages).toEqual(3)
expect(result.data.length).toEqual(3)
expect(result.meta.currentPage).toEqual(1)
expect(result.meta.itemsPerPage).toEqual(3)
})
}

if (process.env.DB !== 'postgres') {
Expand Down Expand Up @@ -3178,5 +3319,49 @@ describe('paginate', () => {
expect(result.links.current).toBe('?page=1&limit=20&sortBy=home.countCat:ASC')
})
})

it('should return correct amount of data with complete correct columns in each data item when data queried using manual sql aggregates functions', async () => {
const salesQuery = saleRepo
.createQueryBuilder('sale')
.select([
'sale.itemName as itemName',
'SUM(sale.totalPrice) as totalSales',
'COUNT(*) as numberOfSales',
])
.groupBy('sale.itemName')

const config: PaginateConfig<SaleEntity> = {
sortableColumns: ['itemName'],
getDataAsRaw: true,
rowCountAsItIs: true,
}

const query: PaginateQuery = {
path: '',
limit: 3,
page: 1,
}

const result = await paginate<SaleEntity>(query, salesQuery, config)

type MyRow = {
itemName: string
totalSales: number
numberOfSales: number
}

result.data.forEach((data) => {
const sale = data as unknown as MyRow
expect(sale.itemName).toBeDefined()
expect(sale.totalSales).toBeDefined()
expect(sale.numberOfSales).toBeDefined()
})

expect(result.meta.totalItems).toEqual(8)
expect(result.meta.totalPages).toEqual(3)
expect(result.data.length).toEqual(3)
expect(result.meta.currentPage).toEqual(1)
expect(result.meta.itemsPerPage).toEqual(3)
})
}
})
41 changes: 36 additions & 5 deletions src/paginate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export interface PaginateConfig<T> {
origin?: string
ignoreSearchByInQueryParam?: boolean
ignoreSelectInQueryParam?: boolean
getDataAsRaw?: boolean
rowCountAsItIs?: boolean
}

export enum PaginationLimit {
Expand Down Expand Up @@ -212,7 +214,6 @@ export async function paginate<T extends ObjectLiteral>(
let [items, totalItems]: [T[], number] = [[], 0]

const queryBuilder = isRepository(repo) ? repo.createQueryBuilder('__root') : repo

if (isRepository(repo) && !config.relations && config.loadEagerRelations === true) {
if (!config.relations) {
FindOptionsUtils.joinEagerRelations(queryBuilder, queryBuilder.alias, repo.metadata)
Expand Down Expand Up @@ -388,11 +389,21 @@ export async function paginate<T extends ObjectLiteral>(
}

if (query.limit === PaginationLimit.COUNTER_ONLY) {
totalItems = await queryBuilder.getCount()
} else if (isPaginated) {
;[items, totalItems] = await queryBuilder.getManyAndCount()
totalItems = await getCount(queryBuilder, config)
} else {
items = await queryBuilder.getMany()
if (!isPaginated && !config.getDataAsRaw) {
items = await queryBuilder.getMany()
}
if (!isPaginated && config.getDataAsRaw) {
items = await queryBuilder.getRawMany()
}
if (isPaginated && !config.getDataAsRaw) {
;[items, totalItems] = await queryBuilder.getManyAndCount()
}
if (isPaginated && config.getDataAsRaw) {
items = await queryBuilder.getRawMany()
totalItems = await getCount(queryBuilder, config)
}
}

const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
Expand Down Expand Up @@ -463,3 +474,23 @@ export async function paginate<T extends ObjectLiteral>(

return Object.assign(new Paginated<T>(), results)
}

export async function getCount<T extends ObjectLiteral>(
qb: SelectQueryBuilder<T>,
config: PaginateConfig<T>
): Promise<number> {
if (!config.rowCountAsItIs) {
return qb.getCount()
}

const sql = qb.orderBy().limit().offset().take().skip().getQuery()

const result = await qb
.createQueryBuilder()
.select('COUNT(*)', 'total_rows')
.from(`(${sql})`, 'query_count')
.setParameters(qb.getParameters())
.getRawOne()

return Number(result.total_rows)
}