diff --git a/packages/nc-gui/composables/useData.ts b/packages/nc-gui/composables/useData.ts index 6139ddd2a9..595099e069 100644 --- a/packages/nc-gui/composables/useData.ts +++ b/packages/nc-gui/composables/useData.ts @@ -550,10 +550,7 @@ export function useData(args: { try { const row = formattedData.value[rowIndex] if (!row.rowMeta.new) { - const id = meta?.value?.columns - ?.filter((c) => c.pk) - .map((c) => row.row[c.title!]) - .join('___') + const id = extractPkFromRow(row.row, meta?.value?.columns) const fullRecord = await $api.dbTableRow.read( NOCO, diff --git a/packages/nc-gui/composables/useKanbanViewStore.ts b/packages/nc-gui/composables/useKanbanViewStore.ts index cf8e78cd2d..3195996dca 100644 --- a/packages/nc-gui/composables/useKanbanViewStore.ts +++ b/packages/nc-gui/composables/useKanbanViewStore.ts @@ -659,10 +659,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( } if (!row.rowMeta.new) { - const id = (meta?.value?.columns as ColumnType[]) - ?.filter((c) => c.pk) - .map((c) => row.row[c.title!]) - .join('___') + const id = extractPkFromRow(row.row, meta?.value?.columns) const deleted = await deleteRowById(id as string) if (!deleted) { diff --git a/packages/nc-gui/composables/useLTARStore.ts b/packages/nc-gui/composables/useLTARStore.ts index 5f13d2720a..74f54d9665 100644 --- a/packages/nc-gui/composables/useLTARStore.ts +++ b/packages/nc-gui/composables/useLTARStore.ts @@ -88,18 +88,10 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState( return metas.value?.[colOptions.value?.fk_related_model_id as string] }) - const rowId = computed(() => - meta.value.columns - .filter((c: Required) => c.pk) - .map((c: Required) => row?.value?.row?.[c.title]) - .join('___'), - ) + const rowId = computed(() => extractPkFromRow(row.value.row, meta.value.columns)) const getRelatedTableRowId = (row: Record) => { - return relatedTableMeta.value?.columns - ?.filter((c) => c.pk) - .map((c) => row?.[c.title as string] ?? row?.[c.id as string]) - .join('___') + return extractPkFromRow(row, relatedTableMeta.value?.columns) } // actions diff --git a/packages/nc-gui/utils/dataUtils.ts b/packages/nc-gui/utils/dataUtils.ts index bbe2a7369e..dc03cbb218 100644 --- a/packages/nc-gui/utils/dataUtils.ts +++ b/packages/nc-gui/utils/dataUtils.ts @@ -26,11 +26,13 @@ export const isValidValue = (val: unknown) => { export const extractPkFromRow = (row: Record, columns: ColumnType[]) => { if (!row || !columns) return null - const pkColumns = columns.filter((c) => c.pk) - - if (pkColumns.every((c) => row?.[c.title as string] === null || row?.[c.title as string] === undefined)) return null - - return pkColumns.map((c) => row?.[c.title as string]).join('___') + const pkCols = columns.filter((c: Required) => c.pk) + // if multiple pk columns, join them with ___ and escape _ in id values with \_ to avoid conflicts + if (pkCols.length > 1) { + return pkCols.map((c: Required) => row?.[c.title]?.toString?.().replaceAll('_', '\\_') ?? null).join('___') + } else if (pkCols.length) { + return row?.[pkCols[0].title] ?? null + } } export const rowPkData = (row: Record, columns: ColumnType[]) => { diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index dc4247f560..ce12efc89d 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -6517,7 +6517,11 @@ class BaseModelSqlv2 { for (const pk of this.model.primaryKeys) { pkValues[pk.title] = data[pk.title] ?? data[pk.column_name]; } - return asString ? Object.values(pkValues).join('___') : pkValues; + return asString + ? Object.values(pkValues) + .map((val) => val?.toString?.().replaceAll('_', '\\_')) + .join('___') + : pkValues; } else if (this.model.primaryKey) { return ( data[this.model.primaryKey.title] ?? @@ -10170,7 +10174,16 @@ export function _wherePk( return where; } - const ids = Array.isArray(id) ? id : (id + '').split('___'); + let ids = id; + + if (Array.isArray(id)) { + ids = id; + } else if (primaryKeys.length === 1) { + ids = [id]; + } else { + ids = (id + '').split('___').map((val) => val.replaceAll('\\_', '_')); + } + for (let i = 0; i < primaryKeys.length; ++i) { if (primaryKeys[i].dt === 'bytea') { // if column is bytea, then we need to encode the id to hex based on format @@ -10216,7 +10229,16 @@ export function getCompositePkValue(primaryKeys: Column[], row) { if (typeof row !== 'object') return row; - return primaryKeys.map((c) => row[c.title] ?? row[c.column_name]).join('___'); + if (primaryKeys.length > 1) { + return primaryKeys.map((c) => + (row[c.title] ?? row[c.column_name])?.toString?.().replaceAll('_', '\\_'), + ).join('___'); + } + + return ( + primaryKeys[0] && + (row[primaryKeys[0].title] ?? row[primaryKeys[0].column_name]) + ); } export function haveFormulaColumn(columns: Column[]) { diff --git a/packages/nocodb/src/helpers/catchError.ts b/packages/nocodb/src/helpers/catchError.ts index 1cf89641b6..f32dc65cb6 100644 --- a/packages/nocodb/src/helpers/catchError.ts +++ b/packages/nocodb/src/helpers/catchError.ts @@ -795,22 +795,43 @@ export class NcError { id: string | string[] | Record | Record[], args?: NcErrorArgs, ) { + let formatedId: string | string[] = ''; if (!id) { - id = 'unknown'; + formatedId = 'unknown'; } else if (typeof id === 'string') { - id = [id]; + formatedId = [id]; } else if (Array.isArray(id)) { if (id.every((i) => typeof i === 'string')) { - id = id as string[]; + formatedId = id as string[]; } else { - id = id.map((i) => Object.values(i).join('___')); + formatedId = id.map((val) => { + const idsArr = Object.values(val); + if (idsArr.length > 1) { + return idsArr + .map((idVal) => idVal?.toString?.().replaceAll('_', '\\_')) + .join('___'); + } else if (idsArr.length) { + return idsArr[0] as any; + } else { + return 'unknown'; + } + }); } } else { - id = Object.values(id).join('___'); + const idsArr = Object.values(id); + if (idsArr.length > 1) { + formatedId = idsArr + .map((idVal) => idVal?.toString?.().replaceAll('_', '\\_')) + .join('___'); + } else if (idsArr.length) { + formatedId = idsArr[0] as any; + } else { + formatedId = 'unknown'; + } } throw new NcBaseErrorv2(NcErrorType.RECORD_NOT_FOUND, { - params: id, + params: formatedId, ...args, }); } diff --git a/packages/nocodb/src/services/data-table.service.ts b/packages/nocodb/src/services/data-table.service.ts index 7229885660..f3e09463d2 100644 --- a/packages/nocodb/src/services/data-table.service.ts +++ b/packages/nocodb/src/services/data-table.service.ts @@ -315,7 +315,11 @@ export class DataTableService { // if composite primary key then join the values with ___ else pk = model.primaryKeys - .map((pk) => row[pk.title] ?? row[pk.column_name]) + .map((pk) => + (row[pk.title] ?? row[pk.column_name]) + ?.toString?.() + ?.replaceAll('_', '\\_'), + ) .join('___'); // if duplicate then throw error if (keys.has(pk)) {