diff --git a/backend/database/core.ts b/backend/database/core.ts index 13ce40ab..8fb6f7fc 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -266,6 +266,12 @@ export default class DatabaseCore extends DatabaseBase { )) as FieldValueMap[]; } + async deleteAll(schemaName: string, filters: QueryFilter): Promise { + const builder = this.knex!(schemaName); + this.#applyFiltersToBuilder(builder, filters); + return await builder.delete(); + } + async getSingleValues( ...fieldnames: ({ fieldname: string; parent?: string } | string)[] ): Promise> { @@ -444,10 +450,35 @@ export default class DatabaseCore extends DatabaseBase { // {"date": [">=", "2017-09-09", "<=", "2017-11-01"]} // => `date >= 2017-09-09 and date <= 2017-11-01` - const filtersArray = []; + const filtersArray = this.#getFiltersArray(filters); + for (const i in filtersArray) { + const filter = filtersArray[i]; + const field = filter[0] as string; + const operator = filter[1]; + const comparisonValue = filter[2]; + const type = i === '0' ? 'where' : 'andWhere'; + if (operator === '=') { + builder[type](field, comparisonValue); + } else if ( + operator === 'in' && + (comparisonValue as (string | null)[]).includes(null) + ) { + const nonNulls = (comparisonValue as (string | null)[]).filter( + Boolean + ) as string[]; + builder[type](field, operator, nonNulls).orWhere(field, null); + } else { + builder[type](field, operator as string, comparisonValue as string); + } + } + } + + #getFiltersArray(filters: QueryFilter) { + const filtersArray = []; for (const field in filters) { const value = filters[field]; + let operator: string | number = '='; let comparisonValue = value as string | number | (string | number)[]; @@ -477,25 +508,7 @@ export default class DatabaseCore extends DatabaseBase { } } - filtersArray.map((filter) => { - const field = filter[0] as string; - const operator = filter[1]; - const comparisonValue = filter[2]; - - if (operator === '=') { - builder.where(field, comparisonValue); - } else if ( - operator === 'in' && - (comparisonValue as (string | null)[]).includes(null) - ) { - const nonNulls = (comparisonValue as (string | null)[]).filter( - Boolean - ) as string[]; - builder.where(field, operator, nonNulls).orWhere(field, null); - } else { - builder.where(field, operator as string, comparisonValue as string); - } - }); + return filtersArray; } async #getColumnDiff(schemaName: string): Promise { diff --git a/backend/database/tests/testCore.spec.ts b/backend/database/tests/testCore.spec.ts index 13f21d85..cdb07e6b 100644 --- a/backend/database/tests/testCore.spec.ts +++ b/backend/database/tests/testCore.spec.ts @@ -549,3 +549,78 @@ test('CRUD dependent schema', async function (t) { await db.close(); }); + +test('db deleteAll', async (t) => { + const db = await getDb(); + + const emailOne = 'one@temp.com'; + const emailTwo = 'two@temp.com'; + const emailThree = 'three@temp.com'; + + const phoneOne = '1'; + const phoneTwo = '2'; + + const customers = [ + { name: 'customer-a', phone: phoneOne, email: emailOne }, + { name: 'customer-b', phone: phoneOne, email: emailOne }, + { name: 'customer-c', phone: phoneOne, email: emailTwo }, + { name: 'customer-d', phone: phoneOne, email: emailTwo }, + { name: 'customer-e', phone: phoneTwo, email: emailTwo }, + { name: 'customer-f', phone: phoneTwo, email: emailThree }, + { name: 'customer-g', phone: phoneTwo, email: emailThree }, + ]; + + for (const { name, email, phone } of customers) { + await db.insert('Customer', { + name, + email, + phone, + ...getDefaultMetaFieldValueMap(), + }); + } + + // Get total count + t.equal((await db.getAll('Customer')).length, customers.length); + + // Single filter + t.equal( + await db.deleteAll('Customer', { email: emailOne }), + customers.filter((c) => c.email === emailOne).length + ); + t.equal( + (await db.getAll('Customer', { filters: { email: emailOne } })).length, + 0 + ); + + // Multiple filters + t.equal( + await db.deleteAll('Customer', { email: emailTwo, phone: phoneTwo }), + customers.filter( + ({ phone, email }) => email === emailTwo && phone === phoneTwo + ).length + ); + t.equal( + await db.deleteAll('Customer', { email: emailTwo, phone: phoneTwo }), + 0 + ); + + // Includes filters + t.equal( + await db.deleteAll('Customer', { email: ['in', [emailTwo, emailThree]] }), + customers.filter( + ({ email, phone }) => + [emailTwo, emailThree].includes(email) && + !(phone === phoneTwo && email === emailTwo) + ).length + ); + t.equal( + ( + await db.getAll('Customer', { + filters: { email: ['in', [emailTwo, emailThree]] }, + }) + ).length, + 0 + ); + + await db.close(); +}); diff --git a/fyo/core/dbHandler.ts b/fyo/core/dbHandler.ts index da5fedec..3863244a 100644 --- a/fyo/core/dbHandler.ts +++ b/fyo/core/dbHandler.ts @@ -6,7 +6,12 @@ import Observable from 'fyo/utils/observable'; import { translateSchema } from 'fyo/utils/translation'; import { Field, RawValue, SchemaMap } from 'schemas/types'; import { getMapFromList } from 'utils'; -import { DatabaseBase, DatabaseDemuxBase, GetAllOptions } from 'utils/db/types'; +import { + DatabaseBase, + DatabaseDemuxBase, + GetAllOptions, + QueryFilter, +} from 'utils/db/types'; import { schemaTranslateables } from 'utils/translationHelpers'; import { LanguageMap } from 'utils/types'; import { Converter } from './converter'; @@ -207,6 +212,16 @@ export class DatabaseHandler extends DatabaseBase { this.observer.trigger(`delete:${schemaName}`, name); } + async deleteAll(schemaName: string, filters: QueryFilter): Promise { + const count = (await this.#demux.call( + 'deleteAll', + schemaName, + filters + )) as number; + this.observer.trigger(`deleteAll:${schemaName}`, filters); + return count; + } + // Other async exists(schemaName: string, name?: string): Promise { const doesExist = (await this.#demux.call( diff --git a/utils/db/types.ts b/utils/db/types.ts index 22a6fad1..d8e60614 100644 --- a/utils/db/types.ts +++ b/utils/db/types.ts @@ -43,6 +43,8 @@ export abstract class DatabaseBase { // Delete abstract delete(schemaName: string, name: string): Promise; + + abstract deleteAll(schemaName:string, filters:QueryFilter): Promise; // Other abstract close(): Promise;