fix(grid): qualify generated save and copy SQL with database name

This commit is contained in:
zipg authored and GitHub committed 2026-09-21 11:51:34 +08:00
1 parent 865b9fba84
commit 420cb80e65
32 files changed
+910 -123

No files matched your search

@@ -0,0 +1,100 @@
// @vitest-environment happy-dom
import { computed, ref } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useDataGridEditor } from "@/composables/useDataGridEditor";
import type { CellValue } from "@/lib/dataGrid/cellValue";
const mocks = vi.hoisted(() => ({
getConfig: vi.fn(),
prepareDataGridSave: vi.fn(),
executeBatch: vi.fn(),
}));
vi.mock("@/lib/backend/api", () => ({
prepareDataGridSave: mocks.prepareDataGridSave,
executeBatch: mocks.executeBatch,
executeConditionalUpdate: vi.fn(),
cancelConditionalUpdate: vi.fn(),
executeInTransaction: vi.fn(),
executeInManualTransaction: vi.fn(),
executeQuery: vi.fn(),
unlockConnectionWrites: vi.fn(),
lockConnectionWrites: vi.fn(),
connectionWriteUnlockState: vi.fn().mockResolvedValue(0),
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({ getConfig: mocks.getConfig }),
}));
vi.mock("@/stores/historyStore", () => ({
useHistoryStore: () => ({ add: vi.fn() }),
}));
vi.mock("@/stores/productionSafetyStore", () => ({
useProductionSafetyStore: () => ({}),
}));
function createEditor(includeDatabaseNameInSaveSql: boolean) {
const rows = ref<CellValue[][]>([["alpha"]]);
let editor: ReturnType<typeof useDataGridEditor>;
editor = useDataGridEditor({
result: computed(() => ({ columns: ["name"], rows: rows.value })),
editable: computed(() => true),
databaseType: computed(() => "mysql"),
connectionId: computed(() => "connection-1"),
database: computed(() => "dbx"),
tableMeta: computed(() => ({
database: "dbx",
tableName: "filter_probe",
columns: [{ name: "name", data_type: "varchar(50)" }],
primaryKeys: ["id"],
})),
sourceColumns: computed(() => undefined),
includeDatabaseNameInSaveSql: computed(() => includeDatabaseNameInSaveSql),
onExecuteSql: computed(() => undefined),
sql: computed(() => undefined),
searchText: ref(""),
whereFilterInput: ref(""),
currentWhereInput: computed(() => undefined),
orderByInput: ref(""),
rowStatusFilter: ref("all"),
pageSize: ref(100),
currentPage: ref(1),
onCellValueChanged: undefined,
getRowItem: (rowId) => {
if (rowId < 0) return undefined;
const row = rows.value[rowId];
if (!row) return undefined;
const changes = editor.dirtyRows.value.get(rowId);
return {
id: rowId,
sourceIndex: rowId,
data: row.map((value, columnIndex) => (changes?.has(columnIndex) ? (changes.get(columnIndex) ?? null) : value)),
isNew: false,
isDeleted: false,
isDirtyCol: row.map((_, columnIndex) => changes?.has(columnIndex) ?? false),
status: changes?.size ? "edited" : "normal",
};
},
emit: vi.fn(),
});
editor.applyCellValue(0, 0, "changed");
return editor;
}
describe("useDataGridEditor save SQL database qualification", () => {
beforeEach(() => {
mocks.prepareDataGridSave.mockReset();
mocks.executeBatch.mockReset();
mocks.getConfig.mockReset();
mocks.getConfig.mockReturnValue(undefined);
mocks.prepareDataGridSave.mockResolvedValue({ statements: ["UPDATE x"], rollbackStatements: [] });
mocks.executeBatch.mockResolvedValue([]);
});
it("forwards the include-database-name setting to the save statement builder", async () => {
await createEditor(true).saveChanges();
expect(mocks.prepareDataGridSave.mock.calls[0]![0]).toMatchObject({ includeDatabaseName: true });
await createEditor(false).saveChanges();
expect(mocks.prepareDataGridSave.mock.calls[1]![0]).toMatchObject({ includeDatabaseName: false });
});
});
@@ -119,6 +119,8 @@ export interface UseDataGridEditorOptions {
rowStatusFilter: Ref<RowStatusFilter>;
dataGridQuickEntryEnabled?: ComputedRef<boolean>;
confirmDangerousRowDeletion?: ComputedRef<boolean>;
/** `生成 SQL 时包含数据库名` — qualify saved tables with their database. */
includeDatabaseNameInSaveSql?: ComputedRef<boolean>;
initialEditColumn?: ComputedRef<number>;
/** Converts a grid value to the text presented by the cell editor. */
cellEditorText?: (value: CellValue, columnIndex: number) => string;
@@ -246,6 +248,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
rowStatusFilter,
dataGridQuickEntryEnabled = computed(() => false),
confirmDangerousRowDeletion = computed(() => true),
includeDatabaseNameInSaveSql = computed(() => false),
initialEditColumn,
cellEditorText,
normalizeEditorInput,
@@ -1572,6 +1575,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
dirtyRows: [...snapshot.dirtyRows.entries()].map(([rowIndex, changes]) => [rowIndex, [...changes.entries()]] as [number, Array<[number, CellValue]>]),
deletedRows: [...snapshot.deletedRows],
newRows: snapshot.newRows,
includeDatabaseName: includeDatabaseNameInSaveSql.value,
};
}
@@ -1,6 +1,6 @@
import { computed, type ComputedRef, type Ref } from "vue";
import * as api from "@/lib/backend/api";
import { omitDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { applyDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
import { loadObjectDdl } from "@/lib/metadata/objectDdlCache";
import { loadObjectMetadataFacet } from "@/lib/metadata/objectMetadataCache";
@@ -119,7 +119,7 @@ export function useDataGridTableMetadataLoaders(options: DataGridTableMetadataLo
try {
const { ddl } = await loadObjectDdl(request, { force });
const formatDialect = sqlFormatDialectForDbType(options.resolvedDatabaseType.value);
const unqualified = omitDdlDatabaseQualifier(ddl, formatDialect, options.resolvedDatabaseType.value, options.settingsStore.editorSettings.generateSqlIncludeDatabaseName, props.tableMeta?.catalog);
const unqualified = applyDdlDatabaseQualifier(ddl, formatDialect, options.resolvedDatabaseType.value, options.settingsStore.editorSettings.generateSqlIncludeDatabaseName, props.tableMeta?.database, props.tableMeta?.catalog);
state.ddlContent.value = options.settingsStore.editorSettings.generateSqlQuoteIdentifiers ? unqualified : omitDdlIdentifierQuotes(unqualified, formatDialect);
} catch (error: any) {
state.ddlContent.value = `-- Error: ${error}`;