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,
FilterOperator,
isOperator,
getOperatorFn,
getFilterTokens,
OperatorSymbolToFunction,
} from './paginate'
import { PaginateQuery } from './decorator'
import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'
@ -45,7 +45,7 @@ describe('paginate', () => {
repo = connection.getRepository(CatEntity)
cats = await repo.save([
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: 'George', color: 'white', age: 3 }),
repo.create({ name: 'Leche', color: 'white', age: null }),
@ -342,7 +342,7 @@ describe('paginate', () => {
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')
})
@ -356,14 +356,14 @@ describe('paginate', () => {
const query: PaginateQuery = {
path: '',
filter: {
age: '$btw:4,6',
age: '$btw:4,5',
},
}
const result = await paginate<CatEntity>(query, repo, config)
expect(result.data).toStrictEqual([cats[0], cats[2]])
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,6')
expect(result.data).toStrictEqual([cats[1], cats[2]])
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 () => {
@ -382,7 +382,7 @@ describe('paginate', () => {
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')
})
@ -402,7 +402,7 @@ describe('paginate', () => {
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')
})
@ -487,7 +487,7 @@ describe('paginate', () => {
{ operator: '$btw', name: 'Between' },
{ operator: '$not', name: 'Not' },
])('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)
})

View File

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