fix(database): filter match (#1319)
Co-authored-by: Chareice <chareice@live.com>
This commit is contained in:
parent
43db2b641b
commit
9d618315ae
52
packages/core/database/src/__tests__/filter-match.test.ts
Normal file
52
packages/core/database/src/__tests__/filter-match.test.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Database, Model } from '..';
|
||||
import { filterMatch } from '../filter-match';
|
||||
import { mockDatabase } from './index';
|
||||
|
||||
describe('filterMatch', () => {
|
||||
let db: Database;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('filter match', async () => {
|
||||
const Post = db.collection({
|
||||
name: 'posts',
|
||||
fields: [{ type: 'string', name: 'title' }],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
|
||||
const post = await Post.repository.create({
|
||||
values: { title: 't1' },
|
||||
});
|
||||
|
||||
expect(
|
||||
filterMatch(post, {
|
||||
title: 't1',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
filterMatch(post, {
|
||||
$or: [{ title: 't1' }, { title: 't2' }],
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
filterMatch(post, {
|
||||
$and: [{ title: 't1' }, { title: 't2' }],
|
||||
}),
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
filterMatch(post, {
|
||||
title: 't2',
|
||||
}),
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
@ -36,6 +36,7 @@ export class ArrayFieldRepository {
|
||||
async set(
|
||||
options: Transactionable & {
|
||||
values: Array<string | number> | string | number;
|
||||
hooks?: boolean;
|
||||
},
|
||||
) {
|
||||
const { transaction } = options;
|
||||
@ -45,7 +46,19 @@ export class ArrayFieldRepository {
|
||||
});
|
||||
|
||||
instance.set(this.fieldName, lodash.castArray(options.values));
|
||||
|
||||
await instance.save({ transaction });
|
||||
|
||||
if (options.hooks !== false) {
|
||||
await this.emitAfterSave(instance, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected async emitAfterSave(instance, options) {
|
||||
await this.collection.db.emitAsync(`${this.collection.name}.afterSaveWithAssociations`, instance, {
|
||||
...options,
|
||||
});
|
||||
instance.clearChangedWithAssociations();
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
@ -57,6 +70,7 @@ export class ArrayFieldRepository {
|
||||
async toggle(
|
||||
options: Transactionable & {
|
||||
value: string | number;
|
||||
hooks?: boolean;
|
||||
},
|
||||
) {
|
||||
const { transaction } = options;
|
||||
@ -71,6 +85,10 @@ export class ArrayFieldRepository {
|
||||
: [...oldValue, options.value];
|
||||
instance.set(this.fieldName, newValue);
|
||||
await instance.save({ transaction });
|
||||
|
||||
if (options.hooks !== false) {
|
||||
await this.emitAfterSave(instance, options);
|
||||
}
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
@ -82,6 +100,7 @@ export class ArrayFieldRepository {
|
||||
async add(
|
||||
options: Transactionable & {
|
||||
values: Array<string | number> | string | number;
|
||||
hooks?: boolean;
|
||||
},
|
||||
) {
|
||||
const { transaction } = options;
|
||||
@ -95,6 +114,10 @@ export class ArrayFieldRepository {
|
||||
const newValue = [...oldValue, ...lodash.castArray(options.values)];
|
||||
instance.set(this.fieldName, newValue);
|
||||
await instance.save({ transaction });
|
||||
|
||||
if (options.hooks !== false) {
|
||||
await this.emitAfterSave(instance, options);
|
||||
}
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
@ -106,6 +129,7 @@ export class ArrayFieldRepository {
|
||||
async remove(
|
||||
options: Transactionable & {
|
||||
values: Array<string | number> | string | number;
|
||||
hooks?: boolean;
|
||||
},
|
||||
) {
|
||||
const { transaction } = options;
|
||||
@ -117,6 +141,10 @@ export class ArrayFieldRepository {
|
||||
const oldValue = instance.get(this.fieldName) || [];
|
||||
instance.set(this.fieldName, lodash.without(oldValue, ...lodash.castArray(options.values)));
|
||||
await instance.save({ transaction });
|
||||
|
||||
if (options.hooks !== false) {
|
||||
await this.emitAfterSave(instance, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected getInstance(options: Transactionable) {
|
||||
|
@ -63,6 +63,10 @@ export class BelongsToManyField extends RelationField {
|
||||
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
||||
}
|
||||
|
||||
if (!this.options.onDelete) {
|
||||
this.options.onDelete = 'CASCADE';
|
||||
}
|
||||
|
||||
const association = collection.model.belongsToMany(Target, {
|
||||
constraints: false,
|
||||
...omit(this.options, ['name', 'type', 'target']),
|
||||
|
49
packages/core/database/src/filter-match.ts
Normal file
49
packages/core/database/src/filter-match.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { filter } from 'mathjs';
|
||||
|
||||
export function filterMatch(model, where) {
|
||||
if (where.filter !== undefined) {
|
||||
where = filter;
|
||||
}
|
||||
|
||||
// Create an object that maps operator names to functions
|
||||
const operatorFunctions = {
|
||||
$eq: (value, condition) => value === condition,
|
||||
$not: (value, condition) => !filterMatch(model, condition),
|
||||
$gt: (value, condition) => value > condition,
|
||||
$gte: (value, condition) => value >= condition,
|
||||
$lt: (value, condition) => value < condition,
|
||||
$lte: (value, condition) => value <= condition,
|
||||
$ne: (value, condition) => value !== condition,
|
||||
$in: (value, condition) => condition.includes(value),
|
||||
$or: (model, conditions) => Object.values(conditions).some((condition) => filterMatch(model, condition)),
|
||||
$and: (model, conditions) => Object.values(conditions).every((condition) => filterMatch(model, condition)),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(where)) {
|
||||
// Check if the property value contains a logical operator
|
||||
if (operatorFunctions[key] !== undefined) {
|
||||
// Check if the conditions specified in the property value are satisfied
|
||||
if (!operatorFunctions[key](model, value)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Check if the property value is an object (which would contain operators)
|
||||
if (typeof value === 'object') {
|
||||
// Loop through each operator in the property value
|
||||
for (const [operator, condition] of Object.entries(value)) {
|
||||
// Check if the property value satisfies the condition
|
||||
if (!operatorFunctions[operator](model[key], condition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume the default operator is "eq"
|
||||
if (!operatorFunctions['$eq'](model[key], value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -15,4 +15,6 @@ export * from './relation-repository/multiple-relation-repository';
|
||||
export * from './relation-repository/single-relation-repository';
|
||||
export * from './repository';
|
||||
export * from './update-associations';
|
||||
export * from './collection-importer';
|
||||
export * from './filter-match';
|
||||
export * from './field-repository/array-field-repository';
|
||||
|
@ -132,7 +132,12 @@ export class OptionsParser {
|
||||
appends.push(field);
|
||||
} else {
|
||||
// field is model attribute, change attributes to array type
|
||||
if (!Array.isArray(attributes)) attributes = [];
|
||||
if (!Array.isArray(attributes)) {
|
||||
attributes = [];
|
||||
if (this.collection.isParent()) {
|
||||
attributes.push(this.inheritFromSubQuery());
|
||||
}
|
||||
}
|
||||
|
||||
attributes.push(field);
|
||||
}
|
||||
|
@ -87,10 +87,11 @@ export type AssociationKeysToBeUpdate = string[];
|
||||
|
||||
export type Values = any;
|
||||
|
||||
export interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
|
||||
export type CountOptions = Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'> &
|
||||
Transactionable & {
|
||||
filter?: Filter;
|
||||
context?: any;
|
||||
}
|
||||
} & FilterByTk;
|
||||
|
||||
export interface FilterByTk {
|
||||
filterByTk?: TargetKey;
|
||||
@ -225,6 +226,17 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
};
|
||||
}
|
||||
|
||||
if (countOptions?.filterByTk) {
|
||||
options['where'] = {
|
||||
[Op.and]: [
|
||||
options['where'] || {},
|
||||
{
|
||||
[this.collection.filterTargetKey]: options.filterByTk,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const queryOptions: any = {
|
||||
...options,
|
||||
distinct: Boolean(this.collection.model.primaryKeyAttribute),
|
||||
@ -608,7 +620,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
return new RelationRepositoryBuilder<R>(this.collection, association);
|
||||
}
|
||||
|
||||
protected buildQueryOptions(options: any) {
|
||||
public buildQueryOptions(options: any) {
|
||||
const parser = new OptionsParser(options, {
|
||||
collection: this.collection,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user