use hash map for op function lookup

This commit is contained in:
ppetzold 2022-01-27 22:12:52 +01:00
parent 7d85cf94b0
commit 3b09c1c90d
2 changed files with 34 additions and 38 deletions

View File

@ -5,8 +5,8 @@ import {
PaginateConfig, PaginateConfig,
FilterOperator, FilterOperator,
isOperator, isOperator,
getOperatorFn,
getFilterTokens, getFilterTokens,
OperatorSymbolToFunction,
} from './paginate' } from './paginate'
import { PaginateQuery } from './decorator' import { PaginateQuery } from './decorator'
import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm' import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'
@ -45,7 +45,7 @@ describe('paginate', () => {
repo = connection.getRepository(CatEntity) repo = connection.getRepository(CatEntity)
cats = await repo.save([ cats = await repo.save([
repo.create({ name: 'Milo', color: 'brown', age: 6 }), repo.create({ name: 'Milo', color: 'brown', age: 6 }),
repo.create({ name: 'Garfield', color: 'ginger', age: null }), repo.create({ name: 'Garfield', color: 'ginger', age: 5 }),
repo.create({ name: 'Shadow', color: 'black', age: 4 }), repo.create({ name: 'Shadow', color: 'black', age: 4 }),
repo.create({ name: 'George', color: 'white', age: 3 }), repo.create({ name: 'George', color: 'white', age: 3 }),
repo.create({ name: 'Leche', color: 'white', age: null }), repo.create({ name: 'Leche', color: 'white', age: null }),
@ -342,7 +342,7 @@ describe('paginate', () => {
const result = await paginate<CatEntity>(query, repo, config) const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[0], cats[2]]) expect(result.data).toStrictEqual([cats[0], cats[1], cats[2]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$gte:4') expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$gte:4')
}) })
@ -356,14 +356,14 @@ describe('paginate', () => {
const query: PaginateQuery = { const query: PaginateQuery = {
path: '', path: '',
filter: { filter: {
age: '$btw:4,6', age: '$btw:4,5',
}, },
} }
const result = await paginate<CatEntity>(query, repo, config) const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[0], cats[2]]) expect(result.data).toStrictEqual([cats[1], cats[2]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,6') expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,5')
}) })
it('should return result based on is null query', async () => { it('should return result based on is null query', async () => {
@ -382,7 +382,7 @@ describe('paginate', () => {
const result = await paginate<CatEntity>(query, repo, config) const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[1], cats[4]]) expect(result.data).toStrictEqual([cats[4]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$null') expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$null')
}) })
@ -402,7 +402,7 @@ describe('paginate', () => {
const result = await paginate<CatEntity>(query, repo, config) const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[0], cats[2], cats[3]]) expect(result.data).toStrictEqual([cats[0], cats[1], cats[2], cats[3]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null') expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
}) })
@ -487,7 +487,7 @@ describe('paginate', () => {
{ operator: '$btw', name: 'Between' }, { operator: '$btw', name: 'Between' },
{ operator: '$not', name: 'Not' }, { operator: '$not', name: 'Not' },
])('should get operator function $name for "$operator"', ({ operator, name }) => { ])('should get operator function $name for "$operator"', ({ operator, name }) => {
const func = getOperatorFn<CatEntity>(operator as FilterOperator) const func = OperatorSymbolToFunction.get(operator as FilterOperator)
expect(func.name).toStrictEqual(name) expect(func.name).toStrictEqual(name)
}) })

View File

@ -72,28 +72,17 @@ export function isOperator(value: unknown): value is FilterOperator {
return values(FilterOperator).includes(value as any) return values(FilterOperator).includes(value as any)
} }
export function getOperatorFn<T>(op: FilterOperator): (...args: any[]) => FindOperator<T> { export const OperatorSymbolToFunction = new Map<FilterOperator, (...args: any[]) => FindOperator<string>>([
switch (op) { [FilterOperator.EQ, Equal],
case FilterOperator.EQ: [FilterOperator.GT, MoreThan],
return Equal [FilterOperator.GTE, MoreThanOrEqual],
case FilterOperator.GT: [FilterOperator.IN, In],
return MoreThan [FilterOperator.NULL, IsNull],
case FilterOperator.GTE: [FilterOperator.LT, LessThan],
return MoreThanOrEqual [FilterOperator.LTE, LessThanOrEqual],
case FilterOperator.IN: [FilterOperator.BTW, Between],
return In [FilterOperator.NOT, Not],
case FilterOperator.NULL: ])
return IsNull
case FilterOperator.LT:
return LessThan
case FilterOperator.LTE:
return LessThanOrEqual
case FilterOperator.BTW:
return Between
case FilterOperator.NOT:
return Not
}
}
export function getFilterTokens(raw: string): string[] { export function getFilterTokens(raw: string): string[] {
const tokens = [] const tokens = []
@ -125,12 +114,11 @@ export function getFilterTokens(raw: string): string[] {
function parseFilter<T>(query: PaginateQuery, config: PaginateConfig<T>) { function parseFilter<T>(query: PaginateQuery, config: PaginateConfig<T>) {
const filter = {} const filter = {}
for (const column of Object.keys(query.filter)) { for (const column of Object.keys(query.filter)) {
if (!(column in config.filterableColumns)) { if (!(column in config.filterableColumns)) {
continue continue
} }
const allowedOperators = config.filterableColumns[column as Column<T>] const allowedOperators = config.filterableColumns[column]
const input = query.filter[column] const input = query.filter[column]
const statements = !Array.isArray(input) ? [input] : input const statements = !Array.isArray(input) ? [input] : input
for (const raw of statements) { for (const raw of statements) {
@ -147,12 +135,20 @@ function parseFilter<T>(query: PaginateQuery, config: PaginateConfig<T>) {
continue continue
} }
if (isOperator(op1)) { if (isOperator(op1)) {
const args = op1 === FilterOperator.IN || op1 === FilterOperator.BTW ? value.split(',') : value switch (op1) {
filter[column] = case FilterOperator.BTW:
op1 === FilterOperator.BTW ? getOperatorFn<T>(op1)(args[0], args[1]) : getOperatorFn<T>(op1)(args) filter[column] = OperatorSymbolToFunction.get(op1)(...value.split(','))
break
case FilterOperator.IN:
filter[column] = OperatorSymbolToFunction.get(op1)(value.split(','))
break
default:
filter[column] = OperatorSymbolToFunction.get(op1)(value)
break
}
} }
if (isOperator(op2)) { if (isOperator(op2)) {
filter[column] = getOperatorFn<T>(op2)(filter[column]) filter[column] = OperatorSymbolToFunction.get(op2)(filter[column])
} }
} }
} }
@ -236,7 +232,7 @@ export async function paginate<T>(
} }
if (query.filter) { if (query.filter) {
const filter = parseFilter<T>(query, config) const filter = parseFilter(query, config)
queryBuilder.andWhere(new Brackets((qb) => qb.andWhere(filter))) queryBuilder.andWhere(new Brackets((qb) => qb.andWhere(filter)))
} }