mirror of
https://github.com/feathersjs/feathers.git
synced 2025-12-08 19:46:22 +00:00
fix(knex): Add support for extended operators in query builder (#3578)
This commit is contained in:
parent
6d3acbaa26
commit
c355ae3184
@ -63,7 +63,7 @@ The Knex specific adapter options are:
|
||||
- `name {string}` (**required**) - The name of the table
|
||||
- `schema {string}` (_optional_) - The name of the schema table prefix (example: `schema.table`)
|
||||
- `tableOptions {only: boolean` (_optional_) - For PostgreSQL only. Argument for passing options to knex db builder. ONLY keyword is used before the tableName to discard inheriting tables' data. (https://knexjs.org/guide/query-builder.html#common)
|
||||
|
||||
- `extendedOperators {[string]: string}` (_optional_) - A map defining additional operators for the query builder. Example: `{ $fulltext: '@@' }` for PostgreSQL full text search. See [Knex source](https://github.com/knex/knex/blob/master/lib/formatter/wrappingFormatter.js#L10) for operators supported by Knex.
|
||||
|
||||
The [common API options](./common.md#options) are:
|
||||
|
||||
|
||||
@ -50,7 +50,13 @@ export class KnexAdapter<
|
||||
...options.filters,
|
||||
$and: (value: any) => value
|
||||
},
|
||||
operators: [...(options.operators || []), '$like', '$notlike', '$ilike']
|
||||
operators: [
|
||||
...(options.operators || []),
|
||||
...Object.keys(options.extendedOperators || {}),
|
||||
'$like',
|
||||
'$notlike',
|
||||
'$ilike'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
@ -82,6 +88,11 @@ export class KnexAdapter<
|
||||
|
||||
knexify(knexQuery: Knex.QueryBuilder, query: Query = {}, parentKey?: string): Knex.QueryBuilder {
|
||||
const knexify = this.knexify.bind(this)
|
||||
const { extendedOperators = {} } = this.getOptions({} as ServiceParams)
|
||||
const operatorsMap = {
|
||||
...OPERATORS,
|
||||
...extendedOperators
|
||||
}
|
||||
|
||||
return Object.keys(query || {}).reduce((currentQuery, key) => {
|
||||
const value = query[key]
|
||||
@ -110,7 +121,7 @@ export class KnexAdapter<
|
||||
return (currentQuery as any)[method](column, value)
|
||||
}
|
||||
|
||||
const operator = OPERATORS[key as keyof typeof OPERATORS] || '='
|
||||
const operator = operatorsMap[key as keyof typeof operatorsMap] || '='
|
||||
|
||||
return operator === '='
|
||||
? currentQuery.where(column, value)
|
||||
|
||||
@ -8,6 +8,9 @@ export interface KnexAdapterOptions extends AdapterServiceOptions {
|
||||
tableOptions?: {
|
||||
only?: boolean
|
||||
}
|
||||
extendedOperators?: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface KnexAdapterTransaction {
|
||||
|
||||
@ -117,7 +117,15 @@ const clean = async () => {
|
||||
table.boolean('created')
|
||||
return table
|
||||
})
|
||||
|
||||
await db.schema.dropTableIfExists(peopleExtendedOps.fullName)
|
||||
await db.schema.createTable(peopleExtendedOps.fullName, (table) => {
|
||||
table.increments('id')
|
||||
table.string('name')
|
||||
table.integer('age')
|
||||
table.integer('time')
|
||||
table.boolean('created')
|
||||
return table
|
||||
})
|
||||
await db.schema.dropTableIfExists(users.fullName)
|
||||
await db.schema.createTable(users.fullName, (table) => {
|
||||
table.increments('id')
|
||||
@ -181,6 +189,7 @@ type ServiceTypes = {
|
||||
'people-customid': KnexService<Person>
|
||||
users: KnexService<Person>
|
||||
todos: KnexService<Todo>
|
||||
'people-extended-ops': KnexService<Person>
|
||||
}
|
||||
|
||||
class TodoService extends KnexService<Todo> {
|
||||
@ -217,6 +226,16 @@ const todos = new TodoService({
|
||||
name: 'todos'
|
||||
})
|
||||
|
||||
const peopleExtendedOps = new KnexService({
|
||||
Model: db,
|
||||
name: 'people-extended-ops',
|
||||
events: ['testing'],
|
||||
extendedOperators: {
|
||||
$neq: '<>', // Not equal (alternative syntax)
|
||||
$startsWith: 'like' // Same as $like but with a different name
|
||||
}
|
||||
})
|
||||
|
||||
describe('Feathers Knex Service', () => {
|
||||
const app = feathers<ServiceTypes>()
|
||||
.hooks({
|
||||
@ -228,6 +247,7 @@ describe('Feathers Knex Service', () => {
|
||||
.use('people-customid', peopleId)
|
||||
.use('users', users)
|
||||
.use('todos', todos)
|
||||
.use('people-extended-ops', peopleExtendedOps)
|
||||
const peopleService = app.service('people')
|
||||
|
||||
peopleService.hooks({
|
||||
@ -722,7 +742,64 @@ describe('Feathers Knex Service', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('extendedOperators', () => {
|
||||
const extendedService = app.service('people-extended-ops')
|
||||
let testData: Person[]
|
||||
|
||||
beforeEach(async () => {
|
||||
testData = await Promise.all([
|
||||
extendedService.create({
|
||||
name: 'StartWithA',
|
||||
age: 25
|
||||
}),
|
||||
extendedService.create({
|
||||
name: 'MiddleAMiddle',
|
||||
age: 30
|
||||
}),
|
||||
extendedService.create({
|
||||
name: 'EndWithA',
|
||||
age: 35
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
for (const item of testData) {
|
||||
await extendedService.remove(item.id)
|
||||
}
|
||||
} catch (error: unknown) {}
|
||||
})
|
||||
|
||||
it('supports custom operators through extendedOperators option', async () => {
|
||||
// Test the $startsWith custom operator
|
||||
const startsWithResults = await extendedService.find({
|
||||
paginate: false,
|
||||
query: {
|
||||
name: {
|
||||
$startsWith: 'Start%' // LIKE operator with % wildcard
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(startsWithResults.length, 1)
|
||||
assert.strictEqual(startsWithResults[0].name, 'StartWithA')
|
||||
|
||||
// Test that regular operators still work alongside extended ones
|
||||
const combinedResults = await extendedService.find({
|
||||
paginate: false,
|
||||
query: {
|
||||
$and: [{ name: { $neq: 'EndWithA' } }, { age: { $gt: 26 } }]
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(combinedResults.length, 1)
|
||||
assert.strictEqual(combinedResults[0].name, 'MiddleAMiddle')
|
||||
})
|
||||
})
|
||||
|
||||
testSuite(app, errors, 'users')
|
||||
testSuite(app, errors, 'people')
|
||||
testSuite(app, errors, 'people-customid', 'customid')
|
||||
testSuite(app, errors, 'people-extended-ops')
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user