fix(grid): clear identity columns when pasting new rows
This commit is contained in:
1 parent
bc2d32f20f
commit
3c3ab96421
2 files changed
+54
-6
No files matched your search
@@ -36,7 +36,15 @@ vi.mock("@/stores/productionSafetyStore", () => ({
|
||||
useProductionSafetyStore: () => ({}),
|
||||
}));
|
||||
|
||||
function createEditor(sourceColumns?: Array<string | undefined>, confirmDangerousRowDeletion = true, cacheKey?: string, readonlyColumnIndexes?: number[], existingRows: CellValue[][] = [], onCellValueChanged?: (rowId: number, columnIndex: number) => void) {
|
||||
function createEditor(
|
||||
sourceColumns?: Array<string | undefined>,
|
||||
confirmDangerousRowDeletion = true,
|
||||
cacheKey?: string,
|
||||
readonlyColumnIndexes?: number[],
|
||||
existingRows: CellValue[][] = [],
|
||||
onCellValueChanged?: (rowId: number, columnIndex: number) => void,
|
||||
tableColumns?: Array<{ name: string; data_type: string; extra?: string; column_default?: string }>,
|
||||
) {
|
||||
let editor: ReturnType<typeof useDataGridEditor>;
|
||||
const result = ref<{ columns: string[]; rows: CellValue[][] }>({
|
||||
columns: ["first", "hidden", "last"],
|
||||
@@ -51,7 +59,7 @@ function createEditor(sourceColumns?: Array<string | undefined>, confirmDangerou
|
||||
database: computed(() => "app"),
|
||||
tableMeta: computed(() => ({
|
||||
tableName: "people",
|
||||
columns: [
|
||||
columns: tableColumns ?? [
|
||||
{ name: "first", data_type: "varchar" },
|
||||
{ name: "hidden", data_type: "varchar" },
|
||||
{ name: "last", data_type: "varchar" },
|
||||
@@ -388,6 +396,29 @@ describe("useDataGridEditor appendPastedRowsToNewRow", () => {
|
||||
expect(editor.hasPendingChanges.value).toBe(true);
|
||||
});
|
||||
|
||||
it("clears generated key columns instead of pasting the copied value", () => {
|
||||
const editor = createEditor(undefined, true, undefined, undefined, [], undefined, [
|
||||
{ name: "first", data_type: "integer", extra: "autoincrement" },
|
||||
{ name: "hidden", data_type: "varchar" },
|
||||
{ name: "last", data_type: "varchar" },
|
||||
]);
|
||||
|
||||
const result = editor.appendPastedRowsToNewRow(
|
||||
-1,
|
||||
[
|
||||
["1", "Lovelace"],
|
||||
["2", "Hopper"],
|
||||
],
|
||||
[0, 2],
|
||||
);
|
||||
|
||||
expect(result).toEqual({ ok: true, rowCount: 2 });
|
||||
expect(editor.newRows.value).toEqual([
|
||||
[null, null, "Lovelace"],
|
||||
[null, null, "Hopper"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps explicitly read-only mapped columns out of editing and paste", () => {
|
||||
const editor = createEditor(["first", "hidden", "last"], true, undefined, [0]);
|
||||
|
||||
|
||||
@@ -1167,10 +1167,16 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
reusableNewRowCount++;
|
||||
}
|
||||
}
|
||||
const shouldClearColumn = clonedColumnClearPredicate();
|
||||
const mappedRows = pastedRows.map((pastedRow, rowIndex) => {
|
||||
const nextRow = rowIndex < reusableNewRowCount ? nextRows[targetNewIndex! + rowIndex]! : emptyDraftRow();
|
||||
for (let columnOffset = 0; columnOffset < Math.min(pastedRow.length, targetColumns.length); columnOffset++) {
|
||||
const columnIndex = targetColumns[columnOffset]!;
|
||||
// Pasting a copied row into a new row must not reuse its generated key.
|
||||
if (shouldClearColumn(columnIndex)) {
|
||||
nextRow[columnIndex] = null;
|
||||
continue;
|
||||
}
|
||||
const value = pastedRow[columnOffset];
|
||||
nextRow[columnIndex] = value === null ? null : coerceCellValue(value, nextRow[columnIndex], columnIndex);
|
||||
}
|
||||
@@ -1205,15 +1211,26 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
}
|
||||
|
||||
function clonedRowData(item: RowItem, resolvedValues?: ReadonlyMap<number, CellValue>): CellValue[] {
|
||||
const columnInfoByName = new Map((tableMeta.value?.columns ?? []).map((column) => [column.name.toLowerCase(), column]));
|
||||
const shouldClearColumn = clonedColumnClearPredicate();
|
||||
return item.data.map((val, i) => {
|
||||
const columnName = sourceColumns.value?.[i] ?? result.value.columns[i];
|
||||
const columnInfo = columnInfoByName.get(columnName.toLowerCase());
|
||||
if (shouldClearClonedColumn(columnName, columnInfo)) return null;
|
||||
if (shouldClearColumn(i)) return null;
|
||||
return resolvedValues?.has(i) ? (resolvedValues.get(i) ?? null) : val;
|
||||
});
|
||||
}
|
||||
|
||||
// Columns generated by the database (auto increment / identity / sequence
|
||||
// defaults) must not be carried over when a row's values are copied into a
|
||||
// new row, otherwise saving fails on a duplicate key. Both the clone-row and
|
||||
// the clipboard-paste paths resolve the columns through this predicate.
|
||||
function clonedColumnClearPredicate(): (columnIndex: number) => boolean {
|
||||
const columnInfoByName = new Map((tableMeta.value?.columns ?? []).map((column) => [column.name.toLowerCase(), column]));
|
||||
return (columnIndex: number) => {
|
||||
const columnName = sourceColumns.value?.[columnIndex] ?? result.value.columns[columnIndex];
|
||||
if (columnName === undefined) return false;
|
||||
return shouldClearClonedColumn(columnName, columnInfoByName.get(columnName.toLowerCase()));
|
||||
};
|
||||
}
|
||||
|
||||
function shouldClearClonedColumn(columnName: string, columnInfo: ColumnInfo | undefined): boolean {
|
||||
if (usesSyntheticRowIdKey(resolvedDatabaseType.value, [columnName])) return true;
|
||||
if (resolvedDatabaseType.value === "neo4j" && columnName === DBX_NEO4J_ELEMENT_ID_COLUMN) return true;
|
||||
|
||||
Reference in new issue
Block a user