fix(transfer): allow unquoted gaussdb transfer identifiers
This commit is contained in:
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5066,6 +5066,7 @@ export default withEnglishFallback({
|
||||
modeOverwrite: "上書き (TRUNCATE + INSERT)",
|
||||
modeUpsert: "アップサート (主キーでINSERT or UPDATE)",
|
||||
targetTableNameCase: "対象テーブル名の大小文字",
|
||||
quoteTargetColumnNames: "対象列名を引用",
|
||||
tableNameCasePreserve: "そのまま",
|
||||
tableNameCaseLower: "小文字に変換",
|
||||
tableNameCaseUpper: "大文字に変換",
|
||||
|
||||
@@ -4730,6 +4730,7 @@ export default withEnglishFallback({
|
||||
modeOverwrite: "덮어쓰기 (TRUNCATE + INSERT)",
|
||||
modeUpsert: "Upsert (PK 기준 INSERT 또는 UPDATE)",
|
||||
targetTableNameCase: "대상 테이블 이름 대소문자",
|
||||
quoteTargetColumnNames: "대상 열 이름 인용",
|
||||
tableNameCasePreserve: "유지",
|
||||
tableNameCaseLower: "소문자",
|
||||
tableNameCaseUpper: "대문자",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5289,6 +5289,7 @@ export default withEnglishFallback({
|
||||
modeOverwrite: "覆盖 (TRUNCATE + INSERT)",
|
||||
modeUpsert: "更新插入 (按主键 INSERT 或 UPDATE)",
|
||||
targetTableNameCase: "目标表名大小写",
|
||||
quoteTargetColumnNames: "引用目标字段名",
|
||||
tableNameCasePreserve: "保持原样",
|
||||
tableNameCaseLower: "转为小写",
|
||||
tableNameCaseUpper: "转为大写",
|
||||
|
||||
@@ -4383,6 +4383,7 @@ export default withEnglishFallback({
|
||||
overallProgress: "整體進度",
|
||||
dataTransfer: "資料傳輸",
|
||||
targetTableNameCase: "目標資料表名稱大小寫",
|
||||
quoteTargetColumnNames: "引用目標欄位名稱",
|
||||
tableNameCasePreserve: "保留",
|
||||
tableNameCaseLower: "小寫",
|
||||
tableNameCaseUpper: "大寫",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1398,6 +1398,7 @@ export interface TransferTaskConfig {
|
||||
content: TransferContent;
|
||||
mode: TransferMode;
|
||||
targetTableNameCase: TransferTableNameCase;
|
||||
quoteTargetColumnNames: boolean;
|
||||
batchSize: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user