diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index e6af4ad839..6899f7b466 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -2977,6 +2977,8 @@ class BaseModelSqlv2 { } } + await this.validateOptions(col, insertObj); + // validate data if (col?.meta?.validate && col?.validate) { const validate = col.getValidators(); @@ -3208,7 +3210,12 @@ class BaseModelSqlv2 { } async bulkUpdateAll( - args: { where?: string; filterArr?: Filter[]; viewId?: string } = {}, + args: { + where?: string; + filterArr?: Filter[]; + viewId?: string; + skipValidationAndHooks?: boolean; + } = {}, data, { cookie }: { cookie?: any } = {}, ) { @@ -3219,7 +3226,7 @@ class BaseModelSqlv2 { this.clientMeta, this.dbDriver, ); - await this.validate(updateData); + if (!args.skipValidationAndHooks) await this.validate(updateData); const pkValues = await this._extractPksValues(updateData); if (pkValues) { // pk is specified - by pass @@ -3253,7 +3260,7 @@ class BaseModelSqlv2 { ); } - await conditionV2(this, conditionObj, qb); + await conditionV2(this, conditionObj, qb, undefined, true); count = ( await this.execAndParse( @@ -3271,7 +3278,8 @@ class BaseModelSqlv2 { await this.execAndParse(qb, null, { raw: true }); } - await this.afterBulkUpdate(null, count, this.dbDriver, cookie, true); + if (!args.skipValidationAndHooks) + await this.afterBulkUpdate(null, count, this.dbDriver, cookie, true); return count; } catch (e) { @@ -3763,6 +3771,8 @@ class BaseModelSqlv2 { // let cols = Object.keys(this.columns); for (let i = 0; i < this.model.columns.length; ++i) { const column = this.model.columns[i]; + await this.validateOptions(column, columns); + // skip validation if `validate` is undefined or false if (!column?.meta?.validate || !column?.validate) continue; @@ -3797,6 +3807,49 @@ class BaseModelSqlv2 { return true; } + // method for validating otpions if column is single/multi select + private async validateOptions( + column: Column, + insertOrUpdateObject: Record, + ) { + // if SingleSelect or MultiSelect, then validate the options + if ( + !( + column.uidt === UITypes.SingleSelect || + column.uidt === UITypes.MultiSelect + ) + ) { + return; + } + + const options = await column + .getColOptions<{ options: SelectOption[] }>() + .then(({ options }) => options.map((opt) => opt.title)); + const columnTitle = column.title; + const columnName = column.column_name; + const columnValue = + insertOrUpdateObject?.[columnTitle] ?? insertOrUpdateObject?.[columnName]; + if (!columnValue) { + return; + } + + // if multi select, then split the values + const columnValueArr = + column.uidt === UITypes.MultiSelect + ? columnValue.split(',') + : [columnValue]; + for (let j = 0; j < columnValueArr.length; ++j) { + const val = columnValueArr[j]; + if (!options.includes(val)) { + NcError.badRequest( + `Invalid option "${val}" provided for column "${columnTitle}". Valid options are "${options.join( + ', ', + )}"`, + ); + } + } + } + async addChild({ colId, rowId, diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 569433d55a..0a6e904059 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -546,7 +546,10 @@ export class ColumnsService { ]); } else { await baseModel.bulkUpdateAll( - { where: `(${column.title},eq,${option.title})` }, + { + where: `(${column.title},eq,${option.title})`, + skipValidationAndHooks: true, + }, { [column.column_name]: null }, { cookie }, ); @@ -728,7 +731,10 @@ export class ColumnsService { ]); } else { await baseModel.bulkUpdateAll( - { where: `(${column.title},eq,${option.title})` }, + { + where: `(${column.title},eq,${option.title})`, + skipValidationAndHooks: true, + }, { [column.column_name]: newOp.title }, { cookie }, ); @@ -814,7 +820,10 @@ export class ColumnsService { ]); } else { await baseModel.bulkUpdateAll( - { where: `(${column.title},eq,${ch.temp_title})` }, + { + where: `(${column.title},eq,${ch.temp_title})`, + skipValidationAndHooks: true, + }, { [column.column_name]: newOp.title }, { cookie }, ); diff --git a/packages/nocodb/tests/unit/factory/column.ts b/packages/nocodb/tests/unit/factory/column.ts index a65fa3fc69..21740b1bc9 100644 --- a/packages/nocodb/tests/unit/factory/column.ts +++ b/packages/nocodb/tests/unit/factory/column.ts @@ -148,13 +148,13 @@ const customColumns = function (type: string, options: any = {}) { column_name: 'SingleSelect', title: 'SingleSelect', uidt: UITypes.SingleSelect, - dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", }, { column_name: 'MultiSelect', title: 'MultiSelect', uidt: UITypes.MultiSelect, - dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", }, ]; case 'custom': diff --git a/packages/nocodb/tests/unit/rest/tests/filter.test.ts b/packages/nocodb/tests/unit/rest/tests/filter.test.ts index e9144aee4f..318aab464a 100644 --- a/packages/nocodb/tests/unit/rest/tests/filter.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/filter.test.ts @@ -519,13 +519,13 @@ function filterSelectBased() { column_name: 'SingleSelect', title: 'SingleSelect', uidt: UITypes.SingleSelect, - dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", }, { column_name: 'MultiSelect', title: 'MultiSelect', uidt: UITypes.MultiSelect, - dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", }, ], });