feat: added operators for postgres array column type (#826)
This commit is contained in:
parent
88cd95295d
commit
ee86e8bdf6
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ coverage/
|
||||
npm-debug.log
|
||||
.history
|
||||
.idea/
|
||||
.env
|
||||
|
3
jest.setup.ts
Normal file
3
jest.setup.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
dotenv.config({ path: '.env' })
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"@types/node": "^20.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
@ -3484,12 +3485,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
@ -10950,9 +10954,9 @@
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ee-first": {
|
||||
|
10
package.json
10
package.json
@ -8,7 +8,7 @@
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"description": "Pagination and filtering helper method for TypeORM repostiories or query builders using Nest.js framework.",
|
||||
"description": "Pagination and filtering helper method for TypeORM repositories or query builders using Nest.js framework.",
|
||||
"keywords": [
|
||||
"nestjs",
|
||||
"typeorm",
|
||||
@ -32,15 +32,16 @@
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/testing": "^10.2.10",
|
||||
"@nestjs/platform-express": "^10.2.10",
|
||||
"@nestjs/common": "^10.2.10",
|
||||
"@nestjs/platform-express": "^10.2.10",
|
||||
"@nestjs/testing": "^10.2.10",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.10",
|
||||
"@types/lodash": "^4.14.201",
|
||||
"@types/node": "^20.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
@ -78,7 +79,8 @@
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
"testEnvironment": "node",
|
||||
"setupFiles": ["<rootDir>/../jest.setup.ts"]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
16
src/__tests__/cat-hair.entity.ts
Normal file
16
src/__tests__/cat-hair.entity.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity()
|
||||
export class CatHairEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column({ type: 'text', array: true, default: '{}' })
|
||||
colors: string[]
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: string
|
||||
}
|
@ -18,6 +18,7 @@ import {
|
||||
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
|
||||
import { PaginateQuery } from './decorator'
|
||||
import {
|
||||
checkIsArray,
|
||||
checkIsEmbedded,
|
||||
checkIsRelation,
|
||||
extractVirtualProperty,
|
||||
@ -162,6 +163,8 @@ export function addWhereCondition<T>(qb: SelectQueryBuilder<T>, column: string,
|
||||
const { isVirtualProperty, query: virtualQuery } = extractVirtualProperty(qb, columnProperties)
|
||||
const isRelation = checkIsRelation(qb, columnProperties.propertyPath)
|
||||
const isEmbedded = checkIsEmbedded(qb, columnProperties.propertyPath)
|
||||
const isArray = checkIsArray(qb, columnProperties.propertyName)
|
||||
|
||||
const alias = fixColumnAlias(columnProperties, qb.alias, isRelation, isVirtualProperty, isEmbedded, virtualQuery)
|
||||
filter[column].forEach((columnFilter: Filter, index: number) => {
|
||||
const columnNamePerIteration = `${columnProperties.column}${index}`
|
||||
@ -175,6 +178,9 @@ export function addWhereCondition<T>(qb: SelectQueryBuilder<T>, column: string,
|
||||
const parameters = fixQueryParam(alias, columnNamePerIteration, columnFilter, condition, {
|
||||
[columnNamePerIteration]: columnFilter.findOperator.value,
|
||||
})
|
||||
if (isArray && condition.parameters?.length && !['not', 'isNull'].includes(condition.operator)) {
|
||||
condition.parameters[0] = `cardinality(${condition.parameters[0]})`
|
||||
}
|
||||
if (columnFilter.comparator === FilterComparator.OR) {
|
||||
qb.orWhere(qb['createWhereConditionExpression'](condition), parameters)
|
||||
} else {
|
||||
|
@ -126,6 +126,13 @@ export function checkIsEmbedded(qb: SelectQueryBuilder<unknown>, propertyPath: s
|
||||
return !!qb?.expressionMap?.mainAlias?.metadata?.hasEmbeddedWithPropertyPath(propertyPath)
|
||||
}
|
||||
|
||||
export function checkIsArray(qb: SelectQueryBuilder<unknown>, propertyName: string): boolean {
|
||||
if (!qb || !propertyName) {
|
||||
return false
|
||||
}
|
||||
return !!qb?.expressionMap?.mainAlias?.metadata.findColumnWithPropertyName(propertyName)?.isArray
|
||||
}
|
||||
|
||||
// This function is used to fix the column alias when using relation, embedded or virtual properties
|
||||
export function fixColumnAlias(
|
||||
properties: ColumnProperties,
|
||||
|
@ -18,6 +18,8 @@ import {
|
||||
} from './filter'
|
||||
import { ToyShopEntity } from './__tests__/toy-shop.entity'
|
||||
import { ToyShopAddressEntity } from './__tests__/toy-shop-address.entity'
|
||||
import * as process from 'process'
|
||||
import { CatHairEntity } from './__tests__/cat-hair.entity'
|
||||
|
||||
const isoStringToDate = (isoString) => new Date(isoString)
|
||||
|
||||
@ -25,6 +27,7 @@ describe('paginate', () => {
|
||||
let dataSource: DataSource
|
||||
let catRepo: Repository<CatEntity>
|
||||
let catToyRepo: Repository<CatToyEntity>
|
||||
let catHairRepo: Repository<CatHairEntity>
|
||||
let toyShopRepo: Repository<ToyShopEntity>
|
||||
let toyShopAddressRepository: Repository<ToyShopAddressEntity>
|
||||
let catHomeRepo: Repository<CatHomeEntity>
|
||||
@ -36,17 +39,18 @@ describe('paginate', () => {
|
||||
let toysShops: ToyShopEntity[]
|
||||
let catHomes: CatHomeEntity[]
|
||||
let catHomePillows: CatHomePillowEntity[]
|
||||
let catHairs: CatHairEntity[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
dataSource = new DataSource({
|
||||
...(process.env.DB === 'postgres'
|
||||
? {
|
||||
type: 'postgres',
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
username: 'root',
|
||||
password: 'pass',
|
||||
database: 'test',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: +process.env.DB_PORT || 5432,
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || 'pass',
|
||||
database: process.env.DB_DATABASE || 'test',
|
||||
}
|
||||
: {
|
||||
type: 'sqlite',
|
||||
@ -61,6 +65,7 @@ describe('paginate', () => {
|
||||
CatHomeEntity,
|
||||
CatHomePillowEntity,
|
||||
ToyShopEntity,
|
||||
process.env.DB === 'postgres' ? CatHairEntity : undefined,
|
||||
],
|
||||
})
|
||||
await dataSource.initialize()
|
||||
@ -163,6 +168,18 @@ describe('paginate', () => {
|
||||
|
||||
// add friends to Milo
|
||||
await catRepo.save({ ...cats[0], friends: cats.slice(1) })
|
||||
|
||||
catHairs = []
|
||||
|
||||
if (process.env.DB === 'postgres') {
|
||||
catHairRepo = dataSource.getRepository(CatHairEntity)
|
||||
catHairs = await catHairRepo.save([
|
||||
catHairRepo.create({ name: 'short', colors: ['white', 'brown', 'black'] }),
|
||||
catHairRepo.create({ name: 'long', colors: ['white', 'brown'] }),
|
||||
catHairRepo.create({ name: 'buzzed', colors: ['white'] }),
|
||||
catHairRepo.create({ name: 'none' }),
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
if (process.env.DB === 'postgres') {
|
||||
@ -2813,6 +2830,61 @@ describe('paginate', () => {
|
||||
})
|
||||
})
|
||||
|
||||
if (process.env.DB === 'postgres') {
|
||||
describe('should return results for an array column', () => {
|
||||
it.each`
|
||||
operator | data | expectedIndexes
|
||||
${'$not:$null'} | ${undefined} | ${[0, 1, 2, 3]}
|
||||
${'$lt'} | ${2} | ${[2, 3]}
|
||||
${'$lte'} | ${2} | ${[1, 2, 3]}
|
||||
${'$btw'} | ${'1,2'} | ${[1, 2]}
|
||||
${'$gte'} | ${2} | ${[0, 1]}
|
||||
${'$gt'} | ${2} | ${[0]}
|
||||
`('with $operator operator', async ({ operator, data, expectedIndexes }) => {
|
||||
const config: PaginateConfig<CatHairEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
filterableColumns: {
|
||||
colors: true,
|
||||
},
|
||||
}
|
||||
|
||||
const queryFilter = `${operator}${data ? `:${data}` : ''}`
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
filter: {
|
||||
colors: queryFilter,
|
||||
},
|
||||
}
|
||||
|
||||
const result = await paginate<CatHairEntity>(query, catHairRepo, config)
|
||||
|
||||
expect(result.meta.filter).toStrictEqual({
|
||||
colors: queryFilter,
|
||||
})
|
||||
expect(result.data).toStrictEqual(expectedIndexes.map((index) => catHairs[index]))
|
||||
expect(result.links.current).toBe(`?page=1&limit=20&sortBy=id:ASC&filter.colors=${queryFilter}`)
|
||||
})
|
||||
|
||||
it('should work with search', async () => {
|
||||
const config: PaginateConfig<CatHairEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
searchableColumns: ['colors'],
|
||||
}
|
||||
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
search: 'brown',
|
||||
}
|
||||
|
||||
const result = await paginate<CatHairEntity>(query, catHairRepo, config)
|
||||
|
||||
expect(result.meta.search).toStrictEqual('brown')
|
||||
expect(result.data).toStrictEqual([catHairs[0], catHairs[1]])
|
||||
expect(result.links.current).toBe(`?page=1&limit=20&sortBy=id:ASC&search=brown`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (process.env.DB !== 'postgres') {
|
||||
describe('should return result based on virtual column', () => {
|
||||
it('should return result sorted and filter by a virtual column in main entity', async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user