fix(sync): constrain TDengine and IoTDB targets to append writes (#891)

## Summary

- expose append-only capability for TDengine and IoTDB targets
- constrain the delivery UI to INSERT/append and remove delete
propagation
- block mutation and delete configurations during backend preflight
- add Go and frontend regression coverage

## Verification

- Go sync capability and preflight regression tests passed
- frontend delivery and Wails DTO regression tests passed
- frontend production build passed
- Wails desktop build passed

Closes #883
This commit is contained in:
Syngnat
2026-08-09 15:56:59 +08:00
committed by GitHub
13 changed files with 148 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ type MigrationCapability struct {
SupportsAutoCreate bool `json:"supportsAutoCreate"`
SupportsAutoAddColumns bool `json:"supportsAutoAddColumns"`
RequiresExistingTarget bool `json:"requiresExistingTarget"`
SupportsMutations bool `json:"supportsMutations"`
}
// ResolveMigrationCapability returns the migration runtime's effective support
@@ -28,6 +29,12 @@ func ResolveMigrationCapability(sourceConfig connection.ConnectionConfig, target
TargetType: targetType,
SourceModel: classifyMigrationDataModel(sourceType),
TargetModel: classifyMigrationDataModel(targetType),
// Most writable targets support the job engine's update/delete semantics.
// Time-series targets are deliberately narrowed below.
SupportsMutations: true,
}
if targetType == "tdengine" || targetType == "iotdb" {
capability.SupportsMutations = false
}
syncConfig := SyncConfig{
SourceConfig: sourceConfig,

View File

@@ -21,6 +21,7 @@ func TestResolveMigrationCapability_MySQLToPostgresUsesFullPlanner(t *testing.T)
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
SupportsMutations: true,
}
if got != want {
@@ -44,6 +45,7 @@ func TestResolveMigrationCapability_PostgresToKingbaseUsesSameFamilyPlanner(t *t
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
SupportsMutations: true,
}
if got != want {
@@ -67,6 +69,7 @@ func TestResolveMigrationCapability_OracleToSQLServerUsesExistingTargetCompatibi
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
SupportsMutations: true,
}
if got != want {
@@ -90,6 +93,7 @@ func TestResolveMigrationCapability_MongoToOracleReportsPlannedNonExecutablePath
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
SupportsMutations: true,
}
if got != want {
@@ -113,6 +117,7 @@ func TestResolveMigrationCapability_RedisToMongoReportsFullKeyspaceBridge(t *tes
SupportsAutoCreate: true,
SupportsAutoAddColumns: false,
RequiresExistingTarget: false,
SupportsMutations: true,
}
if got != want {
@@ -136,6 +141,7 @@ func TestResolveMigrationCapability_MongoToRedisReportsFullKeyspaceBridge(t *tes
SupportsAutoCreate: true,
SupportsAutoAddColumns: false,
RequiresExistingTarget: false,
SupportsMutations: true,
}
if got != want {
@@ -159,6 +165,7 @@ func TestResolveMigrationCapability_CustomPostgresUsesResolvedDriverFamily(t *te
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
SupportsMutations: true,
}
if got != want {
@@ -182,6 +189,7 @@ func TestResolveMigrationCapability_KafkaToQdrantIsUnsupported(t *testing.T) {
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
SupportsMutations: true,
}
if got != want {
@@ -200,6 +208,20 @@ func TestResolveMigrationCapability_RedisToNonMongoTargetIsUnsupported(t *testin
}
}
func TestResolveMigrationCapability_TimeSeriesTargetsAreAppendOnly(t *testing.T) {
for _, targetType := range []string{"tdengine", "iotdb"} {
t.Run(targetType, func(t *testing.T) {
capability := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "mysql"},
connection.ConnectionConfig{Type: targetType},
)
if !capability.CanExecute || capability.SupportsMutations {
t.Fatalf("expected %s target to be executable and append-only, got %+v", targetType, capability)
}
})
}
}
func TestResolveMigrationCapability_GoldenDBUsesMySQLPlannerFamily(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "goldendb"},