feat(transfer): add upsert mode for data transfer (#105)

Add a third transfer mode that inserts new rows and updates existing
rows based on primary key, supporting PostgreSQL, MySQL, SQLite,
SQL Server, Oracle, and DuckDB with database-specific upsert syntax.
Also fixes DuckDB primary key detection in transfer operations.
This commit is contained in:
t8y2
2026-05-06 20:44:05 +08:00
parent 695dc07402
commit 6a3e8067e1
6 changed files with 222 additions and 42 deletions
+16 -8
View File
@@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { useConnectionStore } from "@/stores/connectionStore";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import * as api from "@/lib/api";
import type { TransferProgress } from "@/lib/api";
import type { TransferProgress, TransferMode } from "@/lib/api";
import type { DatabaseType } from "@/types/database";
import { nextTransferTerminalState } from "@/lib/transferProgressState";
import { ArrowRightLeft, Check, X, Loader2, Square, CheckSquare } from "lucide-vue-next";
@@ -47,7 +47,7 @@ const targetSchema = ref("");
// Options
const createTable = ref(true);
const truncateBefore = ref(false);
const transferMode = ref<TransferMode>("append");
const batchSize = ref(1000);
// Transfer state
@@ -188,7 +188,7 @@ function resetState() {
targetDatabases.value = [];
targetSchema.value = "";
createTable.value = true;
truncateBefore.value = false;
transferMode.value = "append";
batchSize.value = 1000;
isTransferring.value = false;
transferProgress.value.clear();
@@ -230,7 +230,7 @@ async function startTransfer() {
targetSchema: targetSchema.value,
tables: [...selectedTables.value],
createTable: createTable.value,
truncateBefore: truncateBefore.value,
mode: transferMode.value,
batchSize: batchSize.value,
};
@@ -437,10 +437,18 @@ const totalTransferred = computed(() =>
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
{{ t("transfer.createTable") }}
</div>
<div class="flex items-center gap-2 cursor-pointer text-xs" @click="truncateBefore = !truncateBefore">
<CheckSquare v-if="truncateBefore" class="w-3.5 h-3.5 text-primary shrink-0" />
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
{{ t("transfer.truncateBefore") }}
<div class="flex items-center gap-3">
<Label class="text-xs shrink-0">{{ t("transfer.transferMode") }}</Label>
<Select v-model="transferMode">
<SelectTrigger class="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="append">{{ t("transfer.modeAppend") }}</SelectItem>
<SelectItem value="overwrite">{{ t("transfer.modeOverwrite") }}</SelectItem>
<SelectItem value="upsert">{{ t("transfer.modeUpsert") }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="flex items-center gap-3">
<Label class="text-xs shrink-0">{{ t("transfer.batchSize") }}</Label>
+4 -1
View File
@@ -555,7 +555,10 @@ export default {
selectAll: "Select All",
deselectAll: "Deselect All",
createTable: "Create table if not exists",
truncateBefore: "Truncate target table before insert",
transferMode: "Transfer mode",
modeAppend: "Append (INSERT)",
modeOverwrite: "Overwrite (TRUNCATE + INSERT)",
modeUpsert: "Upsert (INSERT or UPDATE by PK)",
batchSize: "Batch size",
start: "Start Transfer",
cancel: "Cancel",
+4 -1
View File
@@ -547,7 +547,10 @@ export default {
selectAll: "全选",
deselectAll: "取消全选",
createTable: "目标表不存在时自动建表",
truncateBefore: "插入前清空目标表",
transferMode: "传输模式",
modeAppend: "追加 (INSERT)",
modeOverwrite: "覆盖 (TRUNCATE + INSERT)",
modeUpsert: "更新插入 (按主键 INSERT 或 UPDATE)",
batchSize: "每批行数",
start: "开始传输",
cancel: "取消",
+1
View File
@@ -137,6 +137,7 @@ export type {
SqlFileProgress,
TransferRequest,
TransferProgress,
TransferMode,
TableImportMode,
TableImportStatus,
TableImportColumnMapping,
+3 -1
View File
@@ -450,6 +450,8 @@ export async function listenSqlFileProgress(handler: (progress: SqlFileProgress)
}
// --- Data Transfer ---
export type TransferMode = "append" | "overwrite" | "upsert";
export interface TransferRequest {
transferId: string;
sourceConnectionId: string;
@@ -460,7 +462,7 @@ export interface TransferRequest {
targetSchema: string;
tables: string[];
createTable: boolean;
truncateBefore: boolean;
mode: TransferMode;
batchSize: number;
}