Browse Source

fix: escape _ in id value if composite pk

pull/9683/head
Pranav C 1 month ago
parent
commit
1107111e6d
  1. 6
      packages/nc-gui/composables/useData.ts
  2. 5
      packages/nc-gui/composables/useKanbanViewStore.ts
  3. 15
      packages/nc-gui/composables/useLTARStore.ts
  4. 12
      packages/nc-gui/utils/dataUtils.ts
  5. 17
      packages/nocodb/src/db/BaseModelSqlv2.ts
  6. 33
      packages/nocodb/src/helpers/catchError.ts
  7. 6
      packages/nocodb/src/services/data-table.service.ts

6
packages/nc-gui/composables/useData.ts

@ -1,6 +1,7 @@
import type { ColumnType, LinkToAnotherRecordType, PaginatedType, RelationTypes, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, PaginatedType, RelationTypes, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk' import { UITypes, isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { extractPkFromRow } from '../utils/dataUtils'
import type { CellRange } from '#imports' import type { CellRange } from '#imports'
export function useData(args: { export function useData(args: {
@ -550,10 +551,7 @@ export function useData(args: {
try { try {
const row = formattedData.value[rowIndex] const row = formattedData.value[rowIndex]
if (!row.rowMeta.new) { if (!row.rowMeta.new) {
const id = meta?.value?.columns const id = extractPkFromRow(row.row, meta?.value?.columns)
?.filter((c) => c.pk)
.map((c) => row.row[c.title!])
.join('___')
const fullRecord = await $api.dbTableRow.read( const fullRecord = await $api.dbTableRow.read(
NOCO, NOCO,

5
packages/nc-gui/composables/useKanbanViewStore.ts

@ -659,10 +659,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
} }
if (!row.rowMeta.new) { if (!row.rowMeta.new) {
const id = (meta?.value?.columns as ColumnType[]) const id = extractPkFromRow(row.row, meta?.value?.columns)
?.filter((c) => c.pk)
.map((c) => row.row[c.title!])
.join('___')
const deleted = await deleteRowById(id as string) const deleted = await deleteRowById(id as string)
if (!deleted) { if (!deleted) {

15
packages/nc-gui/composables/useLTARStore.ts

@ -7,6 +7,7 @@ import type {
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { RelationTypes, UITypes, dateFormats, parseStringDateTime, timeFormats } from 'nocodb-sdk' import { RelationTypes, UITypes, dateFormats, parseStringDateTime, timeFormats } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { extractPkFromRow } from '../utils/dataUtils'
interface DataApiResponse { interface DataApiResponse {
list: Record<string, any> list: Record<string, any>
@ -88,18 +89,12 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
return metas.value?.[colOptions.value?.fk_related_model_id as string] return metas.value?.[colOptions.value?.fk_related_model_id as string]
}) })
const rowId = computed(() => const rowId = computed(() => {
meta.value.columns extractPkFromRow(row.value.row, meta.value)
.filter((c: Required<ColumnType>) => c.pk) })
.map((c: Required<ColumnType>) => row?.value?.row?.[c.title])
.join('___'),
)
const getRelatedTableRowId = (row: Record<string, any>) => { const getRelatedTableRowId = (row: Record<string, any>) => {
return relatedTableMeta.value?.columns return extractPkFromRow(row, relatedTableMeta.value?.columns)
?.filter((c) => c.pk)
.map((c) => row?.[c.title as string] ?? row?.[c.id as string])
.join('___')
} }
// actions // actions

12
packages/nc-gui/utils/dataUtils.ts

@ -26,11 +26,13 @@ export const isValidValue = (val: unknown) => {
export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => { export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => {
if (!row || !columns) return null if (!row || !columns) return null
const pkColumns = columns.filter((c) => c.pk) const pkCols = columns.filter((c: Required<ColumnType>) => c.pk)
// if multiple pk columns, join them with ___ and escape _ in id values with \_ to avoid conflicts
if (pkColumns.every((c) => row?.[c.title as string] === null || row?.[c.title as string] === undefined)) return null if (pkCols.length > 1) {
pkCols.map((c: Required<ColumnType>) => row?.[c.title]?.toString?.().replaceAll('_', '\\_') ?? null).join('___')
return pkColumns.map((c) => row?.[c.title as string]).join('___') } else if (pkCols.length) {
return row?.[pkCols[0].title] ?? null
}
} }
export const rowPkData = (row: Record<string, any>, columns: ColumnType[]) => { export const rowPkData = (row: Record<string, any>, columns: ColumnType[]) => {

17
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -6517,7 +6517,11 @@ class BaseModelSqlv2 {
for (const pk of this.model.primaryKeys) { for (const pk of this.model.primaryKeys) {
pkValues[pk.title] = data[pk.title] ?? data[pk.column_name]; 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) { } else if (this.model.primaryKey) {
return ( return (
data[this.model.primaryKey.title] ?? data[this.model.primaryKey.title] ??
@ -10225,7 +10229,16 @@ export function getCompositePkValue(primaryKeys: Column[], row) {
if (typeof row !== 'object') return row; if (typeof row !== 'object') return row;
return primaryKeys.map((c) => row[c.title] ?? row[c.column_name]).join('___'); if (primaryKeys.length > 1) {
primaryKeys.map((c) =>
(row[c.title] ?? row[c.column_name])?.toString?.().replaceAll('_', '\\_'),
);
}
return (
primaryKeys[0] &&
(row[primaryKeys[0].title] ?? row[primaryKeys[0].column_name])
);
} }
export function haveFormulaColumn(columns: Column[]) { export function haveFormulaColumn(columns: Column[]) {

33
packages/nocodb/src/helpers/catchError.ts

@ -795,22 +795,43 @@ export class NcError {
id: string | string[] | Record<string, string> | Record<string, string>[], id: string | string[] | Record<string, string> | Record<string, string>[],
args?: NcErrorArgs, args?: NcErrorArgs,
) { ) {
let formatedId: string | string[] = '';
if (!id) { if (!id) {
id = 'unknown'; formatedId = 'unknown';
} else if (typeof id === 'string') { } else if (typeof id === 'string') {
id = [id]; formatedId = [id];
} else if (Array.isArray(id)) { } else if (Array.isArray(id)) {
if (id.every((i) => typeof i === 'string')) { if (id.every((i) => typeof i === 'string')) {
id = id as string[]; formatedId = id as string[];
} else { } 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];
} else {
return 'unknown';
}
});
} }
} else { } else {
id = Object.values(id).join('___'); const idsArr = Object.values(id);
if (idsArr.length > 1) {
idsArr
.map((idVal) => idVal?.toString?.().replaceAll('_', '\\_'))
.join('___');
} else if (idsArr.length) {
formatedId = idsArr[0];
} else {
formatedId = 'unknown';
}
} }
throw new NcBaseErrorv2(NcErrorType.RECORD_NOT_FOUND, { throw new NcBaseErrorv2(NcErrorType.RECORD_NOT_FOUND, {
params: id, params: formatedId,
...args, ...args,
}); });
} }

6
packages/nocodb/src/services/data-table.service.ts

@ -315,7 +315,11 @@ export class DataTableService {
// if composite primary key then join the values with ___ // if composite primary key then join the values with ___
else else
pk = model.primaryKeys pk = model.primaryKeys
.map((pk) => row[pk.title] ?? row[pk.column_name]) .map((pk) =>
(row[pk.title] ?? row[pk.column_name])
?.toString?.()
?.replaceAll('_', '\\_'),
)
.join('___'); .join('___');
// if duplicate then throw error // if duplicate then throw error
if (keys.has(pk)) { if (keys.has(pk)) {

Loading…
Cancel
Save