Skip to content

Commit 9e69349

Browse files
committed
fix(json-api-nestjs-typeorm): Fix mysql like error
Mysql do not support ilike, use for it like fixes: #104
1 parent c2c9dbb commit 9e69349

File tree

15 files changed

+516
-47
lines changed

15 files changed

+516
-47
lines changed

.env

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
DB_HOST=localhost
22
DB_LOGGING=1
3+
DB_NAME="json-api-db"
34

45
DB_USERNAME="postgres"
56
DB_PASSWORD="postgres"
6-
#DB_NAME="json-api-db"
7-
DB_NAME="postgres"
87
DB_PORT=5432
98
DB_TYPE=postgres
109

1110
#DB_USERNAME="root"
12-
#DB_PASSWORD="password"
13-
#DB_NAME="example_new"
11+
#DB_PASSWORD="mysql"
1412
#DB_PORT=3306
1513
#DB_TYPE=mysql
1614

1715

18-
ORM_TYPE=microorm
19-
#ORM_TYPE=typeorm
16+
#ORM_TYPE=microorm
17+
ORM_TYPE=typeorm

apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('GET method:', () => {
3232
beforeAll(async () => {
3333
jsonSdk = creatSdk();
3434

35-
const addressesPromise = Array.from(new Array(2)).map(() => {
35+
const addressesPromise = Array.from(new Array(5)).map(() => {
3636
const address = new Addresses();
3737
address.city = faker.string.alpha(50);
3838
address.state = faker.string.alpha(50);
@@ -61,7 +61,7 @@ describe('GET method:', () => {
6161
const addressIndex = index % 2 === 0 ? 0 : 1;
6262
const user = getUser();
6363
user.isActive = !!addressIndex;
64-
user.addresses = addressArray[addressIndex];
64+
user.addresses = addressArray[index];
6565
return jsonSdk.jonApiSdkService.postOne(user);
6666
});
6767
usersArray = await Promise.all(usersPromise);
@@ -148,6 +148,16 @@ describe('GET method:', () => {
148148
users2.forEach((user) => {
149149
expect(user.isActive).toBe(false);
150150
});
151+
152+
const resultFindLike = await jsonSdk.jonApiSdkService.getAll(Users, {
153+
filter: {
154+
target: {
155+
login: { [FilterOperand.like]: users2.at(0)?.login.slice(5, -5) },
156+
},
157+
},
158+
});
159+
expect(resultFindLike.length).toBe(1);
160+
expect(resultFindLike.at(0)?.id).toBe(users2.at(0)?.id);
151161
});
152162

153163
it('Should be get entities with filter by relation target', async () => {

docker-compose.yaml

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
version: '3.8'
22
services:
3-
# api-gate:
4-
# container_name: api-gate
5-
# restart: always
6-
# build: apps/api-gate/
7-
# ports:
8-
# - "3000:3000"
93
postgres:
104
image: postgres:15.1-alpine
115
restart: always
@@ -29,23 +23,20 @@ services:
2923
- pgadmin:/root/.pgadmin
3024
ports:
3125
- '8000:80'
32-
# redis:
33-
# image: redis:6.2-alpine
34-
# restart: always
35-
# ports:
36-
# - '6379:6379'
37-
# command: redis-server --save 20 1 --loglevel warning
38-
# volumes:
39-
# - redis:/data
40-
# jaeger:
41-
# image: jaegertracing/all-in-one:1.41
42-
# ports:
43-
# - "16686:16686"
44-
# - "14268:14268"
26+
db:
27+
image: mysql:latest
28+
restart: always
29+
environment:
30+
MYSQL_ROOT_PASSWORD: mysql
31+
ports:
32+
- '3306:3306'
33+
volumes:
34+
- mysql-db:/var/lib/mysql
35+
4536
volumes:
4637
db:
4738
driver: local
48-
# redis:
49-
# driver: local
39+
mysql-db:
40+
driver: local
5041
pgadmin:
5142
driver: local

libs/json-api/json-api-nestjs-typeorm/src/lib/service/typeorm-utils.service.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,15 @@ describe('TypeormUtilsService', () => {
363363
throw new Error(`name is not pattern: params_${alias}_\\d{1,}`);
364364
}
365365
const expressionMap = expression.replace(name, EXPRESSION);
366-
const checkFilterOperand = Object.entries(FilterOperand).find(
366+
367+
const checkFilterOperand = Object.entries({
368+
...FilterOperand,
369+
like: 'ilike',
370+
} as any).find(
371+
// @ts-ignore
367372
([key, val]) => OperandsMapExpression[val] === expressionMap
368373
);
374+
369375
if (!checkFilterOperand) {
370376
expect(checkFilterOperand).not.toBe(undefined);
371377
throw new Error(`expression incorrect`);
@@ -580,7 +586,7 @@ describe('TypeormUtilsService', () => {
580586
throw Error('Should be like pattern');
581587
}
582588

583-
expect(second.expression).toBe(`ILIKE :${secondResult[0]}`);
589+
expect(second.expression).toBe(`LIKE :${secondResult[0]}`);
584590
expect(second.alias).toBe('Users__Comments_comments.createdAt');
585591
expect(second.selectInclude).toBe('comments');
586592
if (!Array.isArray(second.params)) {

libs/json-api/json-api-nestjs-typeorm/src/lib/service/typeorm-utils.service.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import {
55
NotFoundException,
66
UnprocessableEntityException,
77
} from '@nestjs/common';
8-
import { EntityMetadata, Equal, In, Repository } from 'typeorm';
8+
import {
9+
EntityMetadata,
10+
Equal,
11+
In,
12+
Repository,
13+
ILike,
14+
FindOperator,
15+
} from 'typeorm';
916
import { RelationMetadata as TypeOrmRelationMetadata } from 'typeorm/metadata/RelationMetadata';
1017
import {
1118
ObjectTyped,
@@ -31,6 +38,7 @@ import {
3138
} from '../type';
3239

3340
import { CURRENT_ENTITY_REPOSITORY } from '../constants';
41+
import { DriverUtils } from 'typeorm/driver/DriverUtils';
3442

3543
type RelationAlias<E> = {
3644
[K in UnionToTuple<RelationKeys<E>>[number] & PropertyKey]: string;
@@ -255,9 +263,18 @@ export class TypeormUtilsService<
255263
for (const [fieldName, filter] of ObjectTyped.entries(filterTarget)) {
256264
if (!filter) continue;
257265
for (const entries of ObjectTyped.entries(filter)) {
258-
const [operand, value] = entries as [FilterOperand, string];
266+
const [operandFromQuery, value] = entries as [FilterOperand, string];
259267
const valueConditional =
260-
operand === FilterOperand.like ? `%${value}%` : value;
268+
operandFromQuery === FilterOperand.like ? `%${value}%` : value;
269+
270+
const operand = DriverUtils.isPostgresFamily(
271+
this.repository.manager.connection.driver
272+
)
273+
? operandFromQuery === FilterOperand.like
274+
? 'ilike'
275+
: operandFromQuery
276+
: operandFromQuery;
277+
261278
const fieldWithAlias = this.getAliasPath(fieldName);
262279
const paramsName = this.getParamName(fieldWithAlias);
263280

libs/json-api/json-api-nestjs-typeorm/src/lib/type.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ export const OperandsMapExpression = {
1717
[FilterOperand.gt]: `> :${EXPRESSION}`,
1818
[FilterOperand.gte]: `>= :${EXPRESSION}`,
1919
[FilterOperand.in]: `IN (:...${EXPRESSION})`,
20-
[FilterOperand.like]: `ILIKE :${EXPRESSION}`,
20+
[FilterOperand.like]: `LIKE :${EXPRESSION}`,
2121
[FilterOperand.lt]: `< :${EXPRESSION}`,
2222
[FilterOperand.lte]: `<= :${EXPRESSION}`,
2323
[FilterOperand.nin]: `NOT IN (:...${EXPRESSION})`,
2424
[FilterOperand.some]: `&& :${EXPRESSION}`,
25+
ilike: `ILIKE :${EXPRESSION}`,
2526
};
2627

2728
export const OperandMapExpressionForNull = {

libs/typeorm-database/src/lib/config-cli.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DataSource, DataSourceOptions } from 'typeorm';
22
import { join } from 'path';
33
import * as process from 'process';
44

5-
const config: DataSourceOptions = {
5+
const configPg: DataSourceOptions = {
66
type: process.env['DB_TYPE'] as 'mysql' | 'postgres',
77
host: process.env['DB_HOST'],
88
port: parseInt(`${process.env['DB_PORT']}`, 10),
@@ -15,23 +15,25 @@ const config: DataSourceOptions = {
1515
...(process.env['DB_TYPE'] === 'mysql' ? { connectorPackage: 'mysql2' } : {}),
1616
};
1717

18-
// const config: DataSourceOptions = {
19-
// type: 'mysql',
20-
// host: 'localhost',
21-
// username: 'root',
22-
// connectorPackage: 'mysql2',
23-
// password: 'password',
24-
// database: 'example_new',
25-
// logging: true,
26-
// migrations: [join(__dirname, '/migrations/**/*{.ts,.js}')],
27-
// entities: [join(__dirname, '/entities/**/*{.ts,.js}')],
28-
// };
18+
const configMysql: DataSourceOptions = {
19+
type: process.env['DB_TYPE'] as 'mysql' | 'postgres',
20+
host: process.env['DB_HOST'],
21+
port: parseInt(`${process.env['DB_PORT']}`, 10),
22+
username: process.env['DB_USERNAME'],
23+
password: process.env['DB_PASSWORD'],
24+
database: process.env['DB_NAME'],
25+
logging: process.env['DB_LOGGING'] === '1',
26+
migrations: [join(__dirname, '/migrations-mysql/**/*{.ts,.js}')],
27+
entities: [join(__dirname, '/entities-mysql/**/*{.ts,.js}')],
28+
connectorPackage: 'mysql2',
29+
};
2930

3031
const configSeeder = {
3132
seeders: ['./libs/database/src/lib/seeders/*.ts'],
3233
defaultSeeder: 'RootSeeder',
3334
};
3435

36+
const config = process.env['DB_TYPE'] === 'mysql' ? configMysql : configPg;
3537
export { config, configSeeder };
3638

3739
export default new DataSource({ ...config, ...configSeeder });
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
PrimaryGeneratedColumn,
3+
OneToOne,
4+
Column,
5+
Entity,
6+
UpdateDateColumn,
7+
} from 'typeorm';
8+
9+
import { Users, IUsers } from '.';
10+
11+
export type IAddresses = Addresses;
12+
13+
@Entity('addresses')
14+
export class Addresses {
15+
@PrimaryGeneratedColumn()
16+
public id!: number;
17+
18+
@Column({
19+
type: 'varchar',
20+
length: 70,
21+
nullable: true,
22+
default: 'NULL',
23+
})
24+
public city!: string;
25+
26+
@Column({
27+
type: 'varchar',
28+
length: 70,
29+
nullable: true,
30+
default: 'NULL',
31+
})
32+
public state!: string;
33+
34+
@Column({
35+
type: 'varchar',
36+
length: 68,
37+
nullable: true,
38+
default: 'NULL',
39+
})
40+
public country!: string;
41+
42+
@Column({
43+
name: 'created_at',
44+
type: 'timestamp',
45+
nullable: true,
46+
})
47+
public createdAt!: Date;
48+
49+
@UpdateDateColumn({
50+
name: 'updated_at',
51+
type: 'timestamp',
52+
nullable: true,
53+
})
54+
public updatedAt!: Date;
55+
56+
@OneToOne(() => Users, (item) => item.addresses)
57+
public user!: IUsers;
58+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
Column,
3+
Entity,
4+
ManyToMany,
5+
PrimaryGeneratedColumn,
6+
UpdateDateColumn,
7+
} from 'typeorm';
8+
9+
import { IUsers, Users } from './users';
10+
11+
export type IBookList = BookList;
12+
13+
@Entity('book_list')
14+
export class BookList {
15+
@PrimaryGeneratedColumn()
16+
public id!: string;
17+
18+
@Column({
19+
type: 'text',
20+
nullable: false,
21+
})
22+
public text!: string;
23+
24+
@Column({
25+
name: 'created_at',
26+
type: 'timestamp',
27+
nullable: true,
28+
})
29+
public createdAt!: Date;
30+
31+
@UpdateDateColumn({
32+
name: 'updated_at',
33+
type: 'timestamp',
34+
nullable: true,
35+
})
36+
public updatedAt!: Date;
37+
38+
@ManyToMany(() => Users, (item) => item.books)
39+
public users!: IUsers[];
40+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
PrimaryGeneratedColumn,
3+
Column,
4+
Entity,
5+
JoinColumn,
6+
ManyToOne,
7+
UpdateDateColumn,
8+
} from 'typeorm';
9+
10+
export enum CommentKind {
11+
Comment = 'COMMENT',
12+
Message = 'MESSAGE',
13+
Note = 'NOTE',
14+
}
15+
16+
import { Users, IUsers } from '.';
17+
18+
export type IComments = Comments;
19+
20+
@Entity('comments')
21+
export class Comments {
22+
@PrimaryGeneratedColumn()
23+
public id!: number;
24+
25+
@Column({
26+
type: 'text',
27+
nullable: false,
28+
})
29+
public text!: string;
30+
31+
@Column({
32+
type: 'enum',
33+
enum: CommentKind,
34+
nullable: false,
35+
})
36+
public kind!: CommentKind;
37+
38+
@Column({
39+
name: 'created_at',
40+
type: 'timestamp',
41+
nullable: true,
42+
})
43+
public createdAt!: Date;
44+
45+
@UpdateDateColumn({
46+
name: 'updated_at',
47+
type: 'timestamp',
48+
nullable: true,
49+
})
50+
public updatedAt!: Date;
51+
52+
@ManyToOne(() => Users, (item: Users) => item.id)
53+
@JoinColumn({
54+
name: 'created_by',
55+
})
56+
public createdBy!: IUsers;
57+
}

0 commit comments

Comments
 (0)