fix(transfer): allow unquoted gaussdb transfer identifiers

This commit is contained in:
zipg authored and GitHub committed 2026-08-31 22:28:40 +08:00
1 parent 0f33449bdf
commit 4baa46ca88
22 files changed
+423 -53

No files matched your search

@@ -9,6 +9,7 @@ import { createTaskLoadTracker } from "./taskLoadTracker";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue";
import ConnectionTreeSelect from "@/components/connection/ConnectionTreeSelect.vue";
import { useConnectionStore } from "@/stores/connectionStore";
@@ -156,6 +157,7 @@ const pendingTargetSchemaPrefill = ref("");
// Options
const transferMode = ref<TransferMode>("append");
const targetTableNameCase = ref<TransferTableNameCase>("preserve");
const quoteTargetColumnNames = ref(true);
const batchSize = ref(1000);
const isSubmitting = ref(false);
const showStartConfirm = ref(false);
@@ -182,6 +184,8 @@ function isMongoConnection(id: string): boolean {
return connectionType(id) === "mongodb";
}
const showTargetColumnQuoteOption = computed(() => ["gaussdb", "opengauss"].includes(connectionType(targetConnectionId.value) ?? ""));
function isCatalogCapable(id: string): boolean {
const config = store.getConfig(id);
return isDorisFamilyCatalogCapable(config?.db_type, config?.driver_profile);
@@ -640,6 +644,7 @@ function resetState(cancelTaskLoad = true) {
transferContent.value = "structureAndData";
transferMode.value = "append";
targetTableNameCase.value = "preserve";
quoteTargetColumnNames.value = true;
batchSize.value = 1000;
isSubmitting.value = false;
showStartConfirm.value = false;
@@ -765,6 +770,7 @@ async function startTransfer() {
objects: buildTransferObjectSelections(selectedObjects.value, treeDisabledGroups.value),
mode: transferMode.value,
targetTableNameCase: targetTableNameCase.value,
quoteTargetColumnNames: quoteTargetColumnNames.value,
ownershipPolicy: "preserve",
batchSize: batchSize.value,
};
@@ -850,6 +856,7 @@ function currentConfig(): TransferTaskConfig {
content: transferContent.value,
mode: transferMode.value,
targetTableNameCase: targetTableNameCase.value,
quoteTargetColumnNames: quoteTargetColumnNames.value,
batchSize: batchSize.value,
};
}
@@ -878,6 +885,7 @@ async function loadTaskIntoForm(task: TransferTask) {
transferContent.value = config.content;
transferMode.value = config.mode;
targetTableNameCase.value = config.targetTableNameCase;
quoteTargetColumnNames.value = config.quoteTargetColumnNames;
batchSize.value = config.batchSize;
pendingSelectedObjectsPrefill.value = Object.keys(config.objects).length > 0 ? JSON.parse(JSON.stringify(config.objects)) : null;
@@ -1263,6 +1271,10 @@ async function saveConfigTask() {
</SelectContent>
</Select>
</div>
<div v-if="showTargetColumnQuoteOption" class="flex items-center gap-3">
<Label for="transfer-quote-target-column-names" class="text-xs shrink-0">{{ t("transfer.quoteTargetColumnNames") }}</Label>
<Switch id="transfer-quote-target-column-names" v-model="quoteTargetColumnNames" size="sm" />
</div>
<div class="flex items-center gap-3">
<Label class="text-xs shrink-0">{{ t("transfer.batchSize") }}</Label>
<Input v-model.number="batchSize" type="number" min="100" max="10000" step="100" class="h-7 text-xs w-24" />
@@ -27,6 +27,7 @@ function transferRequest(transferId: string, tables = ["users"]): TransferReques
createTable: true,
mode: "append",
targetTableNameCase: "preserve",
quoteTargetColumnNames: true,
batchSize: 1000,
};
}
+1
View File
@@ -5305,6 +5305,7 @@ export default {
modeOverwrite: "Overwrite (TRUNCATE + INSERT)",
modeUpsert: "Upsert (INSERT or UPDATE by PK)",
targetTableNameCase: "Target table name case",
quoteTargetColumnNames: "Quote target column names",
tableNameCasePreserve: "Preserve",
tableNameCaseLower: "Lowercase",
tableNameCaseUpper: "Uppercase",
+1
View File
@@ -5036,6 +5036,7 @@ export default withEnglishFallback({
modeOverwrite: "Sobrescribir (TRUNCATE + INSERT)",
modeUpsert: "Upsert (INSERT o UPDATE por PK)",
targetTableNameCase: "Mayúsculas/minúsculas de tabla destino",
quoteTargetColumnNames: "Citar columnas de destino",
tableNameCasePreserve: "Conservar",
tableNameCaseLower: "Convertir a minúsculas",
tableNameCaseUpper: "Convertir a mayúsculas",
+1
View File
@@ -5037,6 +5037,7 @@ export default withEnglishFallback({
modeOverwrite: "Sovrascrivi (TRUNCATE + INSERT)",
modeUpsert: "Aggiorna/Inserisci (INSERT o UPDATE per chiave primaria)",
targetTableNameCase: "Maiuscole/minuscole tabella destinazione",
quoteTargetColumnNames: "Quota colonne di destinazione",
tableNameCasePreserve: "Mantieni",
tableNameCaseLower: "Converti in minuscolo",
tableNameCaseUpper: "Converti in maiuscolo",
+1
View File
@@ -5066,6 +5066,7 @@ export default withEnglishFallback({
modeOverwrite: "上書き (TRUNCATE + INSERT)",
modeUpsert: "アップサート (主キーでINSERT or UPDATE)",
targetTableNameCase: "対象テーブル名の大小文字",
quoteTargetColumnNames: "対象列名を引用",
tableNameCasePreserve: "そのまま",
tableNameCaseLower: "小文字に変換",
tableNameCaseUpper: "大文字に変換",
+1
View File
@@ -4730,6 +4730,7 @@ export default withEnglishFallback({
modeOverwrite: "덮어쓰기 (TRUNCATE + INSERT)",
modeUpsert: "Upsert (PK 기준 INSERT 또는 UPDATE)",
targetTableNameCase: "대상 테이블 이름 대소문자",
quoteTargetColumnNames: "대상 열 이름 인용",
tableNameCasePreserve: "유지",
tableNameCaseLower: "소문자",
tableNameCaseUpper: "대문자",
+1
View File
@@ -5039,6 +5039,7 @@ export default withEnglishFallback({
modeOverwrite: "Sobrescrever (TRUNCATE + INSERT)",
modeUpsert: "Upsert (INSERT ou UPDATE por PK)",
targetTableNameCase: "Maiúsculas/minúsculas da tabela de destino",
quoteTargetColumnNames: "Citar colunas de destino",
tableNameCasePreserve: "Preservar",
tableNameCaseLower: "Converter para minúsculas",
tableNameCaseUpper: "Converter para maiúsculas",
+1
View File
@@ -5289,6 +5289,7 @@ export default withEnglishFallback({
modeOverwrite: "覆盖 (TRUNCATE + INSERT)",
modeUpsert: "更新插入 (按主键 INSERT 或 UPDATE)",
targetTableNameCase: "目标表名大小写",
quoteTargetColumnNames: "引用目标字段名",
tableNameCasePreserve: "保持原样",
tableNameCaseLower: "转为小写",
tableNameCaseUpper: "转为大写",
+1
View File
@@ -4383,6 +4383,7 @@ export default withEnglishFallback({
overallProgress: "整體進度",
dataTransfer: "資料傳輸",
targetTableNameCase: "目標資料表名稱大小寫",
quoteTargetColumnNames: "引用目標欄位名稱",
tableNameCasePreserve: "保留",
tableNameCaseLower: "小寫",
tableNameCaseUpper: "大寫",
+1
View File
@@ -4451,6 +4451,7 @@ export interface TransferRequest {
objects: TransferObjectSelection[];
mode: TransferMode;
targetTableNameCase: TransferTableNameCase;
quoteTargetColumnNames: boolean;
ownershipPolicy?: TransferOwnershipPolicy;
batchSize: number;
}
@@ -31,6 +31,7 @@ function makeConfig(overrides: Partial<TransferTaskConfig> = {}): TransferTaskCo
content: "structureAndData",
mode: "append",
targetTableNameCase: "preserve",
quoteTargetColumnNames: true,
batchSize: 1000,
...overrides,
};
@@ -66,6 +67,21 @@ describe("transferTaskStore", () => {
expect(store.listTasks("folder-1").map((task) => task.id)).toEqual(["task-1"]);
});
it("keeps target column quoting enabled for saved tasks created before the option existed", async () => {
const legacyConfig = makeConfig() as Partial<TransferTaskConfig>;
delete legacyConfig.quoteTargetColumnNames;
vi.mocked(api.loadTransferTaskLibrary).mockResolvedValue({
version: 1,
folders: [],
tasks: [{ id: "legacy", name: "legacy", config: legacyConfig, createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z" }],
} as unknown as TransferTaskLibrary);
const store = useTransferTaskStore();
await store.initFromStorage();
expect(store.tasks[0]?.config.quoteTargetColumnNames).toBe(true);
});
it("refuses to overwrite a persisted library with invalid entries", async () => {
vi.mocked(api.loadTransferTaskLibrary).mockResolvedValue({
version: 1,
@@ -89,6 +89,7 @@ function normalizeTask(raw: unknown): TransferTask | null {
content: config.content ?? "structureAndData",
mode: config.mode ?? "append",
targetTableNameCase: config.targetTableNameCase ?? "preserve",
quoteTargetColumnNames: config.quoteTargetColumnNames ?? true,
batchSize: typeof config.batchSize === "number" && config.batchSize > 0 ? config.batchSize : 1000,
},
createdAt: candidate.createdAt || nowIso(),
+1
View File
@@ -1398,6 +1398,7 @@ export interface TransferTaskConfig {
content: TransferContent;
mode: TransferMode;
targetTableNameCase: TransferTableNameCase;
quoteTargetColumnNames: boolean;
batchSize: number;
}