fix(database): filter match (#1319)

Co-authored-by: Chareice <chareice@live.com>
This commit is contained in:
chenos 2023-01-03 13:38:20 +08:00 committed by GitHub
parent 43db2b641b
commit 9d618315ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 158 additions and 6 deletions

View 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();
});
});

View File

@ -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) {

View File

@ -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']),

View 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;
}

View File

@ -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';

View File

@ -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);
}

View File

@ -87,10 +87,11 @@ export type AssociationKeysToBeUpdate = string[];
export type Values = any;
export interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
filter?: Filter;
context?: any;
}
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,
});