Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement count query type (RAW|ENTITY) #805

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
"paginate"
],
"devDependencies": {
"@nestjs/common": "^7.5.5",
"@nestjs/core": "^7.5.5",
"@nestjs/testing": "^7.0.0",
"@nestjs/typeorm": "^7.1.0",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.0",
"@types/jest": "^27.0.0",
"@types/node": "^17.0.22",
"coveralls": "^3.0.5",
"jest": "^26.6.3",
"mysql": "^2.17.1",
"prettier": "^2.1.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^6.5.2",
"rxjs": "^7.1.0",
"ts-jest": "^26.4.4",
"ts-node": "^10.0.0",
"typeorm": "0.3.6",
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/base-orm-config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { TestPivotEntity } from './test-pivot.entity';
import { TestRelatedEntity } from './test-related.entity';
import { TestEntity } from './test.entity';

export const baseOrmConfigs: TypeOrmModuleOptions = {
entities: [TestEntity, TestRelatedEntity],
entities: [TestEntity, TestRelatedEntity, TestPivotEntity],
host: 'localhost',
port: 3306,
type: 'mysql',
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/paginate-raw-and-entities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Connection, QueryRunner, SelectQueryBuilder } from 'typeorm';
import { paginateRawAndEntities } from '../paginate';
import { Pagination } from '../pagination';
import { baseOrmConfigs } from './base-orm-config';
import { TestPivotEntity } from './test-pivot.entity';
import { TestEntity } from './test.entity';

describe('Test paginateRawAndEntities function', () => {
Expand Down Expand Up @@ -46,6 +47,16 @@ describe('Test paginateRawAndEntities function', () => {
})
.execute();
}

for (let i = 1; i <= 3; i++) {
await queryBuilder
.insert()
.into(TestPivotEntity)
.values({
id: i,
})
.execute();
}
});

afterAll(async () => {
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/paginate-raw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Connection, QueryRunner, SelectQueryBuilder } from 'typeorm';
import { paginateRaw } from '../paginate';
import { Pagination } from '../pagination';
import { baseOrmConfigs } from './base-orm-config';
import { TestPivotEntity } from './test-pivot.entity';
import { TestEntity } from './test.entity';

interface RawQueryResult {
Expand Down Expand Up @@ -48,6 +49,16 @@ describe('Test paginateRaw function', () => {
})
.execute();
}

for (let i = 1; i <= 3; i++) {
await queryBuilder
.insert()
.into(TestPivotEntity)
.values({
id: i,
})
.execute();
}
});

afterAll(async () => {
Expand Down
49 changes: 47 additions & 2 deletions src/__tests__/paginate.query.builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { paginate } from './../paginate';
import { Pagination } from '../pagination';
import { baseOrmConfigs } from './base-orm-config';
import { TestEntity } from './test.entity';
import { PaginationTypeEnum } from '../interfaces';
import { CountQueryTypeEnum, PaginationTypeEnum } from '../interfaces';
import { TestRelatedEntity } from './test-related.entity';
import { TestPivotEntity } from './test-pivot.entity';

describe('Paginate with queryBuilder', () => {
let app: TestingModule;
let connection: Connection;
let runner: QueryRunner;
let queryBuilder: SelectQueryBuilder<TestEntity>;
let testRelatedQueryBuilder: SelectQueryBuilder<TestRelatedEntity>;
let testPivotQueryBuilder: SelectQueryBuilder<TestPivotEntity>;

beforeEach(async () => {
app = await Test.createTestingModule({
Expand All @@ -32,6 +34,10 @@ describe('Paginate with queryBuilder', () => {
TestRelatedEntity,
'tr',
);
testPivotQueryBuilder = runner.manager.createQueryBuilder(
TestPivotEntity,
'tp',
);
});

afterEach(() => {
Expand All @@ -48,7 +54,7 @@ describe('Paginate with queryBuilder', () => {
const result = await paginate(queryBuilder, {
limit: 10,
page: 1,
paginationType: PaginationTypeEnum.LIMIT_AND_OFFSET,
paginationType: PaginationTypeEnum.TAKE_AND_SKIP,
});
expect(result).toBeInstanceOf(Pagination);
});
Expand Down Expand Up @@ -113,4 +119,43 @@ describe('Paginate with queryBuilder', () => {
expect(result).toBeInstanceOf(Pagination);
expect(result.meta.totalItems).toEqual(10);
});

it('Can paginate with countQueryType set to ENTITY', async () => {
const pivot = (await testPivotQueryBuilder
.where('tp.id = :id', { id: 1 })
.getOne()) as TestPivotEntity;

const testOne = await runner.manager
.getRepository(TestEntity)
.findOne({ where: { id: 1 } });
const testTwo = await runner.manager
.getRepository(TestEntity)
.findOne({ where: { id: 2 } });

if (testOne) {
testOne.testPivots = [pivot];
}

if (testTwo) {
testTwo.testPivots = [pivot];
}

await runner.manager.save([testOne, testTwo]);

const qb = queryBuilder.innerJoinAndSelect(
't.testPivots',
'tp',
't_tp.testPivotId = :id',
{ id: 1 },
);

const result = await paginate(qb, {
limit: 10,
page: 1,
countQueryType: CountQueryTypeEnum.ENTITY,
});

expect(result).toBeInstanceOf(Pagination);
expect(result.meta.totalItems).toEqual(2);
});
});
11 changes: 11 additions & 0 deletions src/__tests__/test-pivot.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Entity, ManyToMany, PrimaryColumn } from 'typeorm';
import { TestEntity } from './test.entity';

@Entity()
export class TestPivotEntity {
@PrimaryColumn()
id: number;

@ManyToMany(() => TestEntity, (tests) => tests.testPivots)
tests: TestEntity[];
}
17 changes: 16 additions & 1 deletion src/__tests__/test.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PrimaryColumn, Entity, OneToMany } from 'typeorm';
import { PrimaryColumn, Entity, OneToMany, ManyToMany, JoinTable } from 'typeorm';
import { TestPivotEntity } from './test-pivot.entity';
import { TestRelatedEntity } from './test-related.entity';

@Entity()
Expand All @@ -8,4 +9,18 @@ export class TestEntity {

@OneToMany(() => TestRelatedEntity, (related) => related.test)
related: TestRelatedEntity[];

@ManyToMany(() => TestPivotEntity, (testPivots) => testPivots.tests)
@JoinTable({
name: 'test_test_pivot',
joinColumn: {
name: 'testId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'testPivotId',
referencedColumnName: 'id',
},
})
testPivots: TestPivotEntity[];
}
11 changes: 11 additions & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export enum PaginationTypeEnum {
TAKE_AND_SKIP = 'take',
}

export enum CountQueryTypeEnum {
RAW = 'raw',
ENTITY = 'entity',
}

export interface IPaginationOptions<CustomMetaType = IPaginationMeta> {
/**
* @default 10
Expand Down Expand Up @@ -43,6 +48,12 @@ export interface IPaginationOptions<CustomMetaType = IPaginationMeta> {
*/
countQueries?: boolean;

/**
* @default CountQueryTypeEnum.RAW
* Used for count query with countQuery(builder, cacheOptions) which is RAW or builder.getCount() which is ENTITY
*/
countQueryType?: CountQueryTypeEnum;

/**
* @default false
* @link https://orkhan.gitbook.io/typeorm/docs/caching
Expand Down
70 changes: 59 additions & 11 deletions src/paginate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from 'typeorm';
import { Pagination } from './pagination';
import {
CountQueryTypeEnum,
IPaginationMeta,
IPaginationOptions,
PaginationTypeEnum,
Expand Down Expand Up @@ -51,8 +52,15 @@ export async function paginateRaw<
queryBuilder: SelectQueryBuilder<T>,
options: IPaginationOptions<CustomMetaType>,
): Promise<Pagination<T, CustomMetaType>> {
const [page, limit, route, paginationType, countQueries, cacheOption] =
resolveOptions(options);
const [
page,
limit,
route,
paginationType,
countQueries,
countQueryType,
cacheOption,
] = resolveOptions(options);

const promises: [Promise<T[]>, Promise<number> | undefined] = [
(paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET
Expand All @@ -65,7 +73,10 @@ export async function paginateRaw<
];

if (countQueries) {
promises[1] = countQuery(queryBuilder, cacheOption);
promises[1] =
countQueryType === CountQueryTypeEnum.RAW
? countQuery(queryBuilder, cacheOption)
: queryBuilder.cache(cacheOption).getCount();
}

const [items, total] = await Promise.all(promises);
Expand All @@ -88,8 +99,15 @@ export async function paginateRawAndEntities<
queryBuilder: SelectQueryBuilder<T>,
options: IPaginationOptions<CustomMetaType>,
): Promise<[Pagination<T, CustomMetaType>, Partial<T>[]]> {
const [page, limit, route, paginationType, countQueries, cacheOption] =
resolveOptions(options);
const [
page,
limit,
route,
paginationType,
countQueries,
countQueryType,
cacheOption,
] = resolveOptions(options);

const promises: [
Promise<{ entities: T[]; raw: T[] }>,
Expand All @@ -105,7 +123,10 @@ export async function paginateRawAndEntities<
];

if (countQueries) {
promises[1] = countQuery(queryBuilder, cacheOption);
promises[1] =
countQueryType === CountQueryTypeEnum.RAW
? countQuery(queryBuilder, cacheOption)
: queryBuilder.cache(cacheOption).getCount();
}

const [itemObject, total] = await Promise.all(promises);
Expand All @@ -126,17 +147,34 @@ export async function paginateRawAndEntities<

function resolveOptions(
options: IPaginationOptions<any>,
): [number, number, string, PaginationTypeEnum, boolean, TypeORMCacheType] {
): [
number,
number,
string,
PaginationTypeEnum,
boolean,
CountQueryTypeEnum,
TypeORMCacheType,
] {
const page = resolveNumericOption(options, 'page', DEFAULT_PAGE);
const limit = resolveNumericOption(options, 'limit', DEFAULT_LIMIT);
const route = options.route;
const paginationType =
options.paginationType || PaginationTypeEnum.LIMIT_AND_OFFSET;
const countQueries =
typeof options.countQueries !== 'undefined' ? options.countQueries : true;
const countQueryType = options.countQueryType || CountQueryTypeEnum.RAW;
const cacheQueries = options.cacheQueries || false;

return [page, limit, route, paginationType, countQueries, cacheQueries];
return [
page,
limit,
route,
paginationType,
countQueries,
countQueryType,
cacheQueries,
];
}

function resolveNumericOption(
Expand Down Expand Up @@ -208,8 +246,15 @@ async function paginateQueryBuilder<T, CustomMetaType = IPaginationMeta>(
queryBuilder: SelectQueryBuilder<T>,
options: IPaginationOptions<CustomMetaType>,
): Promise<Pagination<T, CustomMetaType>> {
const [page, limit, route, paginationType, countQueries, cacheOption] =
resolveOptions(options);
const [
page,
limit,
route,
paginationType,
countQueries,
countQueryType,
cacheOption,
] = resolveOptions(options);

const promises: [Promise<T[]>, Promise<number> | undefined] = [
(PaginationTypeEnum.LIMIT_AND_OFFSET === paginationType
Expand All @@ -222,7 +267,10 @@ async function paginateQueryBuilder<T, CustomMetaType = IPaginationMeta>(
];

if (countQueries) {
promises[1] = countQuery(queryBuilder, cacheOption);
promises[1] =
countQueryType === CountQueryTypeEnum.RAW
? countQuery(queryBuilder, cacheOption)
: queryBuilder.cache(cacheOption).getCount();
}

const [items, total] = await Promise.all(promises);
Expand Down