fix(grid): treat bulk-edit NULL keyword as SQL NULL

This commit is contained in:
dienaso authored and GitHub committed 2026-09-22 16:22:07 +08:00
1 parent 7ade5d2b6e
commit 69b43cf401
4 files changed
+84 -5

No files matched your search

@@ -308,7 +308,7 @@ import { uiTuning } from "@/lib/app/uiTuning";
import { useDataGridScrollbars, type DataGridScrollbarsRuntime } from "@/composables/useDataGridScrollbars";
import { useDataGridSelection } from "@/composables/useDataGridSelection";
import { dataGridNavigationOrigin, dataGridPageScrollTop, dataGridRowScrollTop, moveDataGridCell, navigateDataGridCell, type DataGridNavigationDirection, type DataGridScrollAlignment } from "@/lib/dataGrid/dataGridNavigation";
import { dataGridInlineBulkEditValue } from "@/lib/dataGrid/dataGridInlineBulkEdit";
import { bulkEditInputToSqlValue, dataGridInlineBulkEditValue } from "@/lib/dataGrid/dataGridInlineBulkEdit";
import type { CellPosition } from "@/lib/dataGrid/gridSelection";
import type { GridSnapshotSource } from "@/lib/gridSnapshot/gridSnapshot";
import { createDataGridRuntimeScope } from "@/lib/dataGrid/dataGridRuntime";
@@ -8442,7 +8442,8 @@ async function applyBulkEditValue() {
toast(t("grid.conditionalBulkEditConditionRequired"), 5000);
return;
}
const value = bulkEditValue.value === "" ? null : coerceCellValue(bulkEditValue.value, undefined, target.columnIndex);
const rawValue = bulkEditInputToSqlValue(bulkEditValue.value);
const value = rawValue === null ? null : coerceCellValue(rawValue, undefined, target.columnIndex);
const statement = await buildDataGridConditionalUpdateSql({
databaseType: resolvedDatabaseType.value,
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId),
@@ -8464,8 +8465,9 @@ async function applyBulkEditValue() {
return;
}
// Empty input sets the selected cells to SQL NULL (the placeholder hints "Value, or NULL").
const value = bulkEditValue.value === "" ? null : bulkEditValue.value;
// Empty input or the bare keyword NULL sets the selected cells to SQL NULL
// (the placeholder hints "Value, or NULL").
const value = bulkEditInputToSqlValue(bulkEditValue.value);
if (!fillSelectionWithValue(value)) return;
bulkEditDialogOpen.value = false;
}
@@ -1,6 +1,6 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { dataGridInlineBulkEditValue } from "@/lib/dataGrid/dataGridInlineBulkEdit";
import { bulkEditInputToSqlValue, dataGridInlineBulkEditValue } from "@/lib/dataGrid/dataGridInlineBulkEdit";
const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8");
@@ -40,3 +40,19 @@ describe("DataGrid inline bulk editing", () => {
expect(commit).toContain("cancelEdit()");
});
});
describe("bulkEditInputToSqlValue", () => {
it.each(["", " ", "NULL", "null", "Null", "nUlL", " NULL "])("maps the NULL sentinel %j to SQL NULL", (input) => {
expect(bulkEditInputToSqlValue(input)).toBeNull();
});
it.each(["0", "false", "NULLL", "NULLX", "'NULL'", "nulls", "NUL", " 1 "])("keeps non-sentinel input %j as typed", (input) => {
expect(bulkEditInputToSqlValue(input)).toBe(input);
});
it("routes applyBulkEditValue through bulkEditInputToSqlValue for both scopes", () => {
const applyBulkEdit = functionBody("applyBulkEditValue", "confirmConditionalBulkEdit");
expect(applyBulkEdit).toContain("bulkEditInputToSqlValue(bulkEditValue.value)");
expect(applyBulkEdit).not.toContain('bulkEditValue.value === "" ? null');
});
});
@@ -12,3 +12,15 @@ export function dataGridInlineBulkEditValue(event: DataGridInlineBulkEditKeyEven
if (event.key === "Enter") return "";
return event.key !== "Process" && Array.from(event.key).length === 1 ? event.key : undefined;
}
/**
* Bulk-edit dialog contract (placeholder "Value, or NULL"): empty input and the
* bare keyword `NULL` stage SQL NULL. Every other input is kept as typed so a
* text column can still receive the literal word via quoted/user-specific input
* outside this dialog path.
*/
export function bulkEditInputToSqlValue(input: string): string | null {
const trimmed = input.trim();
if (trimmed === "" || trimmed.toUpperCase() === "NULL") return null;
return input;
}