Skip to content

Commit

Permalink
fix(request,typeorm): added new query search operators for case-insen…
Browse files Browse the repository at this point in the history
…sitive db queries

fix nestjsx#77, nestjsx#212
  • Loading branch information
michaelyali authored and yharaskrik committed Dec 24, 2019
1 parent 20cac78 commit bac42f8
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 40 deletions.
2 changes: 1 addition & 1 deletion packages/crud-request/src/request-query.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,6 @@ export class RequestQueryParser implements ParsedRequestParams {
break;
}

return { field: option.field, operator: 'eq', value };
return { field: option.field, operator: '$eq', value };
}
}
62 changes: 41 additions & 21 deletions packages/crud-request/src/types/request-query.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ObjectLiteral } from '@nestjsx/util';

export type QueryFields = string[];

export type QueryFilter = {
Expand All @@ -24,7 +22,9 @@ export type QuerySort = {

export type QuerySortArr = [string, QuerySortOperator];

export type ComparisonOperator =
export type QuerySortOperator = 'ASC' | 'DESC';

type DeprecatedCondOperator =
| 'eq'
| 'ne'
| 'gt'
Expand All @@ -39,31 +39,41 @@ export type ComparisonOperator =
| 'notin'
| 'isnull'
| 'notnull'
| 'between'
| keyof SFieldOperator;
export type QuerySortOperator = 'ASC' | 'DESC';
| 'between';

export enum CondOperator {
EQUALS = 'eq',
NOT_EQUALS = 'ne',
GREATER_THAN = 'gt',
LOWER_THAN = 'lt',
GREATER_THAN_EQUALS = 'gte',
LOWER_THAN_EQUALS = 'lte',
STARTS = 'starts',
ENDS = 'ends',
CONTAINS = 'cont',
EXCLUDES = 'excl',
IN = 'in',
NOT_IN = 'notin',
IS_NULL = 'isnull',
NOT_NULL = 'notnull',
BETWEEN = 'between',
EQUALS = '$eq',
NOT_EQUALS = '$ne',
GREATER_THAN = '$gt',
LOWER_THAN = '$lt',
GREATER_THAN_EQUALS = '$gte',
LOWER_THAN_EQUALS = '$lte',
STARTS = '$starts',
ENDS = '$ends',
CONTAINS = '$cont',
EXCLUDES = '$excl',
IN = '$in',
NOT_IN = '$notin',
IS_NULL = '$isnull',
NOT_NULL = '$notnull',
BETWEEN = '$between',
EQUALS_LOW = '$eqL',
NOT_EQUALS_LOW = '$neL',
STARTS_LOW = '$startsL',
ENDS_LOW = '$endsL',
CONTAINS_LOW = '$contL',
EXCLUDES_LOW = '$exclL',
IN_LOW = '$inL',
NOT_IN_LOW = '$betweenL',
}

export type ComparisonOperator = DeprecatedCondOperator | keyof SFieldOperator;

// new search
export type SPrimitivesVal = string | number | boolean;

export type SFiledValues = SPrimitivesVal | Array<SPrimitivesVal>;

export type SFieldOperator = {
$eq?: SFiledValues;
$ne?: SFiledValues;
Expand All @@ -80,9 +90,19 @@ export type SFieldOperator = {
$between?: SFiledValues;
$isnull?: SFiledValues;
$notnull?: SFiledValues;
$eqL?: SFiledValues;
$neL?: SFiledValues;
$startsL?: SFiledValues;
$endsL?: SFiledValues;
$contL?: SFiledValues;
$exclL?: SFiledValues;
$inL?: SFiledValues;
$notinL?: SFiledValues;
$betweenL?: SFiledValues;
$or?: SFieldOperator;
$and?: never;
};

export type SField = SPrimitivesVal | SFieldOperator;

export type SFields = {
Expand Down
14 changes: 9 additions & 5 deletions packages/crud-request/test/request-query.parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,10 +451,14 @@ describe('#request-query', () => {
};
const test = qp.parseParams(params, options);
const expected = [
{ field: 'foo', operator: 'eq', value: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d' },
{ field: 'bb', operator: 'eq', value: 1 },
{ field: 'buz', operator: 'eq', value: 'string' },
{ field: 'bigInt', operator: 'eq', value: '9007199254740999' },
{
field: 'foo',
operator: '$eq',
value: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d',
},
{ field: 'bb', operator: '$eq', value: 1 },
{ field: 'buz', operator: '$eq', value: 'string' },
{ field: 'bigInt', operator: '$eq', value: '9007199254740999' },
];
expect(test.paramsFilter).toMatchObject(expected);
});
Expand All @@ -468,7 +472,7 @@ describe('#request-query', () => {
bar: { field: 'bar', type: 'number' },
};
const test = qp.parseParams(params, options);
const expected = [{ field: 'bar', operator: 'eq', value: 123 }];
const expected = [{ field: 'bar', operator: '$eq', value: 123 }];
expect(test.paramsFilter).toMatchObject(expected);
});
});
Expand Down
78 changes: 65 additions & 13 deletions packages/crud-typeorm/src/typeorm-crud.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ import {
SelectQueryBuilder,
DeepPartial,
WhereExpression,
ConnectionOptions,
} from 'typeorm';
import { RelationMetadata } from 'typeorm/metadata/RelationMetadata';
import { Param } from '@nestjs/common';

export class TypeOrmCrudService<T> extends CrudService<T> {
protected dbName: ConnectionOptions['type'];
protected entityColumns: string[];
protected entityPrimaryColumns: string[];
protected entityColumnsHash: ObjectLiteral = {};
Expand All @@ -48,6 +49,7 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
constructor(protected repo: Repository<T>) {
super();

this.dbName = this.repo.metadata.connection.options.type;
this.onInitMapEntityColumns();
this.onInitMapRelations();
}
Expand Down Expand Up @@ -814,6 +816,7 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
param: any,
): { str: string; params: ObjectLiteral } {
const field = this.getFieldWithAlias(cond.field);
const likeOperator = this.dbName === 'postgres' ? 'ILIKE' : 'LIKE';
let str: string;
let params: ObjectLiteral;

Expand Down Expand Up @@ -867,18 +870,12 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
break;

case '$in':
/* istanbul ignore if */
if (!Array.isArray(cond.value) || !cond.value.length) {
this.throwBadRequestException(`Invalid column '${cond.field}' value`);
}
this.checkFilterIsArray(cond);
str = `${field} IN (:...${param})`;
break;

case '$notin':
/* istanbul ignore if */
if (!Array.isArray(cond.value) || !cond.value.length) {
this.throwBadRequestException(`Invalid column '${cond.field}' value`);
}
this.checkFilterIsArray(cond);
str = `${field} NOT IN (:...${param})`;
break;

Expand All @@ -893,17 +890,62 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
break;

case '$between':
/* istanbul ignore if */
if (!Array.isArray(cond.value) || !cond.value.length || cond.value.length !== 2) {
this.throwBadRequestException(`Invalid column '${cond.field}' value`);
}
this.checkFilterIsArray(cond, cond.value.length !== 2);
str = `${field} BETWEEN :${param}0 AND :${param}1`;
params = {
[`${param}0`]: cond.value[0],
[`${param}1`]: cond.value[1],
};
break;

// case insensitive
case '$eqL':
str = `LOWER(${field}) = :${param}`;
break;

case '$neL':
str = `LOWER(${field}) != :${param}`;
break;

case '$startsL':
str = `${field} ${likeOperator} :${param}`;
params = { [param]: `${cond.value}%` };
break;

case '$endsL':
str = `${field} ${likeOperator} :${param}`;
params = { [param]: `%${cond.value}` };
break;

case '$contL':
str = `${field} ${likeOperator} :${param}`;
params = { [param]: `%${cond.value}%` };
break;

case '$exclL':
str = `${field} NOT ${likeOperator} :${param}`;
params = { [param]: `%${cond.value}%` };
break;

case '$inL':
this.checkFilterIsArray(cond);
str = `LOWER(${field}) IN (:...${param})`;
break;

case '$notinL':
this.checkFilterIsArray(cond);
str = `LOWER(${field}) NOT IN (:...${param})`;
break;

case '$betweenL':
this.checkFilterIsArray(cond, cond.value.length !== 2);
str = `LOWER(${field}) BETWEEN :${param}0 AND :${param}1`;
params = {
[`${param}0`]: cond.value[0],
[`${param}1`]: cond.value[1],
};
break;

/* istanbul ignore next */
default:
str = `${field} = :${param}`;
Expand All @@ -917,6 +959,16 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
return { str, params };
}

private checkFilterIsArray(cond: QueryFilter, withLength?: boolean) {
if (
!Array.isArray(cond.value) ||
!cond.value.length ||
(!isNil(withLength) ? withLength : false)
) {
this.throwBadRequestException(`Invalid column '${cond.field}' value`);
}
}

private getPrimaryParam(params: any): string {
return objKeys(params).find((param) => params[param] && params[param].primary);
}
Expand Down

0 comments on commit bac42f8

Please sign in to comment.