2023-02-09 08:21:09 +00:00
|
|
|
import { Brackets, FindOperator, SelectQueryBuilder } from 'typeorm'
|
2023-01-30 11:17:14 +00:00
|
|
|
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
|
2023-02-09 08:21:09 +00:00
|
|
|
import { PaginateQuery } from './decorator'
|
2023-01-30 11:17:14 +00:00
|
|
|
import { checkIsRelation, extractVirtualProperty, fixColumnAlias, getPropertiesByColumnName } from './helper'
|
2023-02-09 08:21:09 +00:00
|
|
|
import {
|
|
|
|
FilterComparator,
|
|
|
|
FilterOperator,
|
|
|
|
FilterSuffix,
|
|
|
|
isComparator,
|
|
|
|
isOperator,
|
|
|
|
isSuffix,
|
|
|
|
OperatorSymbolToFunction,
|
|
|
|
} from './operator'
|
|
|
|
import { PaginateConfig } from './paginate'
|
2023-01-30 11:17:14 +00:00
|
|
|
|
2023-02-09 08:21:09 +00:00
|
|
|
type Filter = { comparator: FilterComparator; findOperator: FindOperator<string> }
|
|
|
|
type ColumnsFilters = { [columnName: string]: Filter[] }
|
2023-01-30 11:17:14 +00:00
|
|
|
|
2023-02-09 08:21:09 +00:00
|
|
|
export interface FilterToken {
|
|
|
|
comparator: FilterComparator
|
|
|
|
suffix?: FilterSuffix
|
|
|
|
operator: FilterOperator
|
|
|
|
value: string
|
2023-01-30 11:17:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This function is used to fix the query parameters when using relation, embeded or virtual properties
|
|
|
|
// It will replace the column name with the alias name and return the new parameters
|
2023-02-09 08:21:09 +00:00
|
|
|
export function fixQueryParam(
|
2023-01-30 11:17:14 +00:00
|
|
|
alias: string,
|
|
|
|
column: string,
|
|
|
|
filter: Filter,
|
|
|
|
condition: WherePredicateOperator,
|
|
|
|
parameters: { [key: string]: string }
|
|
|
|
): { [key: string]: string } {
|
|
|
|
const isNotOperator = (condition.operator as string) === 'not'
|
|
|
|
|
|
|
|
const conditionFixer = (
|
|
|
|
alias: string,
|
|
|
|
column: string,
|
|
|
|
filter: Filter,
|
|
|
|
operator: WherePredicateOperator['operator'],
|
|
|
|
parameters: { [key: string]: string }
|
|
|
|
): { condition_params: any; params: any } => {
|
|
|
|
let condition_params: any = undefined
|
|
|
|
let params = parameters
|
|
|
|
switch (operator) {
|
|
|
|
case 'between':
|
|
|
|
condition_params = [alias, `:${column}_from`, `:${column}_to`]
|
|
|
|
params = {
|
2023-02-09 08:21:09 +00:00
|
|
|
[column + '_from']: filter.findOperator.value[0],
|
|
|
|
[column + '_to']: filter.findOperator.value[1],
|
2023-01-30 11:17:14 +00:00
|
|
|
}
|
|
|
|
break
|
|
|
|
case 'in':
|
|
|
|
condition_params = [alias, `:...${column}`]
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
condition_params = [alias, `:${column}`]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return { condition_params, params }
|
|
|
|
}
|
|
|
|
|
|
|
|
const { condition_params, params } = conditionFixer(
|
|
|
|
alias,
|
|
|
|
column,
|
|
|
|
filter,
|
|
|
|
isNotOperator ? condition['condition']['operator'] : condition.operator,
|
|
|
|
parameters
|
|
|
|
)
|
|
|
|
|
|
|
|
if (isNotOperator) {
|
|
|
|
condition['condition']['parameters'] = condition_params
|
|
|
|
} else {
|
|
|
|
condition.parameters = condition_params
|
|
|
|
}
|
|
|
|
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
2023-02-09 08:21:09 +00:00
|
|
|
export function generatePredicateCondition(
|
|
|
|
qb: SelectQueryBuilder<unknown>,
|
|
|
|
column: string,
|
|
|
|
filter: Filter,
|
|
|
|
alias: string,
|
|
|
|
isVirtualProperty = false
|
|
|
|
): WherePredicateOperator {
|
|
|
|
return qb['getWherePredicateCondition'](
|
|
|
|
isVirtualProperty ? column : alias,
|
|
|
|
filter.findOperator
|
|
|
|
) as WherePredicateOperator
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addWhereCondition<T>(qb: SelectQueryBuilder<T>, column: string, filter: ColumnsFilters) {
|
2023-01-30 11:17:14 +00:00
|
|
|
const columnProperties = getPropertiesByColumnName(column)
|
|
|
|
const { isVirtualProperty, query: virtualQuery } = extractVirtualProperty(qb, columnProperties)
|
|
|
|
const isRelation = checkIsRelation(qb, columnProperties.propertyPath)
|
2023-02-09 08:21:09 +00:00
|
|
|
filter[column].forEach((columnFilter: Filter, index: number) => {
|
|
|
|
const columnNamePerIteration = `${column}${index}`
|
|
|
|
const alias = fixColumnAlias(columnProperties, qb.alias, isRelation, isVirtualProperty, virtualQuery)
|
|
|
|
const condition = generatePredicateCondition(qb, column, columnFilter, alias, isVirtualProperty)
|
|
|
|
const parameters = fixQueryParam(alias, columnNamePerIteration, columnFilter, condition, {
|
|
|
|
[columnNamePerIteration]: columnFilter.findOperator.value,
|
|
|
|
})
|
|
|
|
if (columnFilter.comparator === FilterComparator.OR) {
|
|
|
|
qb.orWhere(qb['createWhereConditionExpression'](condition), parameters)
|
|
|
|
} else {
|
|
|
|
qb.andWhere(qb['createWhereConditionExpression'](condition), parameters)
|
|
|
|
}
|
2023-01-30 11:17:14 +00:00
|
|
|
})
|
2023-02-09 08:21:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getFilterTokens(raw?: string): FilterToken | null {
|
|
|
|
if (raw === undefined || raw === null) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
const token: FilterToken = {
|
|
|
|
comparator: FilterComparator.AND,
|
|
|
|
suffix: undefined,
|
|
|
|
operator: FilterOperator.EQ,
|
|
|
|
value: raw,
|
|
|
|
}
|
|
|
|
|
|
|
|
const MAX_OPERTATOR = 4 // max 4 operator es: $and:$not:$eq:$null
|
|
|
|
const OPERAND_SEPARATOR = ':'
|
|
|
|
|
|
|
|
const matches = raw.split(OPERAND_SEPARATOR)
|
|
|
|
const maxOperandCount = matches.length > MAX_OPERTATOR ? MAX_OPERTATOR : matches.length
|
|
|
|
const notValue: (FilterOperator | FilterSuffix | FilterComparator)[] = []
|
|
|
|
|
|
|
|
for (let i = 0; i < maxOperandCount; i++) {
|
|
|
|
const match = matches[i]
|
|
|
|
if (isComparator(match)) {
|
|
|
|
token.comparator = match
|
|
|
|
} else if (isSuffix(match)) {
|
|
|
|
token.suffix = match
|
|
|
|
} else if (isOperator(match)) {
|
|
|
|
token.operator = match
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
notValue.push(match)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (notValue.length) {
|
|
|
|
token.value =
|
|
|
|
token.operator === FilterOperator.NULL
|
|
|
|
? undefined
|
|
|
|
: raw.replace(`${notValue.join(OPERAND_SEPARATOR)}${OPERAND_SEPARATOR}`, '')
|
|
|
|
}
|
|
|
|
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parseFilter<T>(
|
|
|
|
query: PaginateQuery,
|
|
|
|
filterableColumns?: PaginateConfig<T>['filterableColumns']
|
|
|
|
): ColumnsFilters {
|
|
|
|
const filter: ColumnsFilters = {}
|
|
|
|
if (!filterableColumns || !query.filter) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
for (const column of Object.keys(query.filter)) {
|
|
|
|
if (!(column in filterableColumns)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const allowedOperators = filterableColumns[column]
|
|
|
|
const input = query.filter[column]
|
|
|
|
const statements = !Array.isArray(input) ? [input] : input
|
|
|
|
for (const raw of statements) {
|
|
|
|
const token = getFilterTokens(raw)
|
|
|
|
if (
|
|
|
|
!token ||
|
|
|
|
!(
|
|
|
|
allowedOperators.includes(token.operator) ||
|
|
|
|
(token.suffix === FilterSuffix.NOT &&
|
|
|
|
allowedOperators.includes(token.suffix) &&
|
|
|
|
token.operator === FilterOperator.EQ) ||
|
|
|
|
(token.suffix &&
|
|
|
|
allowedOperators.includes(token.suffix) &&
|
|
|
|
allowedOperators.includes(token.operator))
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const params: (typeof filter)[0][0] = {
|
|
|
|
comparator: token.comparator,
|
|
|
|
findOperator: undefined,
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (token.operator) {
|
|
|
|
case FilterOperator.BTW:
|
|
|
|
params.findOperator = OperatorSymbolToFunction.get(token.operator)(...token.value.split(','))
|
|
|
|
break
|
|
|
|
case FilterOperator.IN:
|
|
|
|
params.findOperator = OperatorSymbolToFunction.get(token.operator)(token.value.split(','))
|
|
|
|
break
|
|
|
|
case FilterOperator.ILIKE:
|
|
|
|
params.findOperator = OperatorSymbolToFunction.get(token.operator)(`%${token.value}%`)
|
|
|
|
break
|
|
|
|
case FilterOperator.SW:
|
|
|
|
params.findOperator = OperatorSymbolToFunction.get(token.operator)(`${token.value}%`)
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
params.findOperator = OperatorSymbolToFunction.get(token.operator)(token.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
filter[column] = [...(filter[column] || []), params]
|
|
|
|
|
|
|
|
if (token.suffix) {
|
|
|
|
const lastFilterElement = filter[column].length - 1
|
|
|
|
filter[column][lastFilterElement].findOperator = OperatorSymbolToFunction.get(token.suffix)(
|
|
|
|
filter[column][lastFilterElement].findOperator
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return filter
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addFilter<T>(
|
|
|
|
qb: SelectQueryBuilder<T>,
|
|
|
|
query: PaginateQuery,
|
|
|
|
filterableColumns?: PaginateConfig<T>['filterableColumns']
|
|
|
|
): SelectQueryBuilder<T> {
|
|
|
|
const filter = parseFilter(query, filterableColumns)
|
|
|
|
return qb.andWhere(
|
|
|
|
new Brackets((qb: SelectQueryBuilder<T>) => {
|
|
|
|
for (const column in filter) {
|
|
|
|
addWhereCondition(qb, column, filter)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
2023-01-30 11:17:14 +00:00
|
|
|
}
|