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(
|
async set(
|
||||||
options: Transactionable & {
|
options: Transactionable & {
|
||||||
values: Array<string | number> | string | number;
|
values: Array<string | number> | string | number;
|
||||||
|
hooks?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
@ -45,7 +46,19 @@ export class ArrayFieldRepository {
|
|||||||
});
|
});
|
||||||
|
|
||||||
instance.set(this.fieldName, lodash.castArray(options.values));
|
instance.set(this.fieldName, lodash.castArray(options.values));
|
||||||
|
|
||||||
await instance.save({ transaction });
|
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) => {
|
@transaction((args, transaction) => {
|
||||||
@ -57,6 +70,7 @@ export class ArrayFieldRepository {
|
|||||||
async toggle(
|
async toggle(
|
||||||
options: Transactionable & {
|
options: Transactionable & {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
|
hooks?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
@ -71,6 +85,10 @@ export class ArrayFieldRepository {
|
|||||||
: [...oldValue, options.value];
|
: [...oldValue, options.value];
|
||||||
instance.set(this.fieldName, newValue);
|
instance.set(this.fieldName, newValue);
|
||||||
await instance.save({ transaction });
|
await instance.save({ transaction });
|
||||||
|
|
||||||
|
if (options.hooks !== false) {
|
||||||
|
await this.emitAfterSave(instance, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction((args, transaction) => {
|
@transaction((args, transaction) => {
|
||||||
@ -82,6 +100,7 @@ export class ArrayFieldRepository {
|
|||||||
async add(
|
async add(
|
||||||
options: Transactionable & {
|
options: Transactionable & {
|
||||||
values: Array<string | number> | string | number;
|
values: Array<string | number> | string | number;
|
||||||
|
hooks?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
@ -95,6 +114,10 @@ export class ArrayFieldRepository {
|
|||||||
const newValue = [...oldValue, ...lodash.castArray(options.values)];
|
const newValue = [...oldValue, ...lodash.castArray(options.values)];
|
||||||
instance.set(this.fieldName, newValue);
|
instance.set(this.fieldName, newValue);
|
||||||
await instance.save({ transaction });
|
await instance.save({ transaction });
|
||||||
|
|
||||||
|
if (options.hooks !== false) {
|
||||||
|
await this.emitAfterSave(instance, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction((args, transaction) => {
|
@transaction((args, transaction) => {
|
||||||
@ -106,6 +129,7 @@ export class ArrayFieldRepository {
|
|||||||
async remove(
|
async remove(
|
||||||
options: Transactionable & {
|
options: Transactionable & {
|
||||||
values: Array<string | number> | string | number;
|
values: Array<string | number> | string | number;
|
||||||
|
hooks?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
@ -117,6 +141,10 @@ export class ArrayFieldRepository {
|
|||||||
const oldValue = instance.get(this.fieldName) || [];
|
const oldValue = instance.get(this.fieldName) || [];
|
||||||
instance.set(this.fieldName, lodash.without(oldValue, ...lodash.castArray(options.values)));
|
instance.set(this.fieldName, lodash.without(oldValue, ...lodash.castArray(options.values)));
|
||||||
await instance.save({ transaction });
|
await instance.save({ transaction });
|
||||||
|
|
||||||
|
if (options.hooks !== false) {
|
||||||
|
await this.emitAfterSave(instance, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getInstance(options: Transactionable) {
|
protected getInstance(options: Transactionable) {
|
||||||
|
@ -63,6 +63,10 @@ export class BelongsToManyField extends RelationField {
|
|||||||
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.options.onDelete) {
|
||||||
|
this.options.onDelete = 'CASCADE';
|
||||||
|
}
|
||||||
|
|
||||||
const association = collection.model.belongsToMany(Target, {
|
const association = collection.model.belongsToMany(Target, {
|
||||||
constraints: false,
|
constraints: false,
|
||||||
...omit(this.options, ['name', 'type', 'target']),
|
...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 './relation-repository/single-relation-repository';
|
||||||
export * from './repository';
|
export * from './repository';
|
||||||
export * from './update-associations';
|
export * from './update-associations';
|
||||||
|
export * from './collection-importer';
|
||||||
|
export * from './filter-match';
|
||||||
export * from './field-repository/array-field-repository';
|
export * from './field-repository/array-field-repository';
|
||||||
|
@ -132,7 +132,12 @@ export class OptionsParser {
|
|||||||
appends.push(field);
|
appends.push(field);
|
||||||
} else {
|
} else {
|
||||||
// field is model attribute, change attributes to array type
|
// 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);
|
attributes.push(field);
|
||||||
}
|
}
|
||||||
|
@ -87,10 +87,11 @@ export type AssociationKeysToBeUpdate = string[];
|
|||||||
|
|
||||||
export type Values = any;
|
export type Values = any;
|
||||||
|
|
||||||
export interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
|
export type CountOptions = Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'> &
|
||||||
filter?: Filter;
|
Transactionable & {
|
||||||
context?: any;
|
filter?: Filter;
|
||||||
}
|
context?: any;
|
||||||
|
} & FilterByTk;
|
||||||
|
|
||||||
export interface FilterByTk {
|
export interface FilterByTk {
|
||||||
filterByTk?: TargetKey;
|
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 = {
|
const queryOptions: any = {
|
||||||
...options,
|
...options,
|
||||||
distinct: Boolean(this.collection.model.primaryKeyAttribute),
|
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);
|
return new RelationRepositoryBuilder<R>(this.collection, association);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected buildQueryOptions(options: any) {
|
public buildQueryOptions(options: any) {
|
||||||
const parser = new OptionsParser(options, {
|
const parser = new OptionsParser(options, {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user