Skip to content

Commit 0255cc0

Browse files
committed
fix(data-migrator): add support for Oracle 19c
related to #326
1 parent 91ef95e commit 0255cc0

File tree

2 files changed

+91
-7
lines changed

2 files changed

+91
-7
lines changed

data-migrator/core/src/main/java/io/camunda/migration/data/impl/DataSourceRegistry.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
import com.zaxxer.hikari.HikariDataSource;
1111
import io.camunda.migration.data.config.property.DataSourceProperties;
1212
import io.camunda.migration.data.config.property.MigratorProperties;
13+
import io.camunda.migration.data.impl.clients.C8Client;
1314
import jakarta.annotation.PreDestroy;
15+
import java.sql.SQLException;
1416
import java.util.Optional;
1517
import javax.sql.DataSource;
1618
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
1719
import org.springframework.stereotype.Component;
1820
import org.springframework.transaction.PlatformTransactionManager;
1921
import org.springframework.transaction.support.TransactionTemplate;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2024

2125
/**
2226
* Registry for managing multiple datasources used by the migrator.
@@ -44,12 +48,15 @@
4448
@Component
4549
public class DataSourceRegistry {
4650

51+
protected static final Logger LOGGER = LoggerFactory.getLogger(C8Client.class);
52+
4753
protected final HikariDataSource c7DataSource;
4854
protected final HikariDataSource c8DataSource;
4955
protected final DataSource migratorDataSource;
5056
protected final PlatformTransactionManager c7TransactionManager;
5157
protected final PlatformTransactionManager migratorTransactionManager;
5258
protected final TransactionTemplate migratorTransactionTemplate;
59+
protected Boolean isOracle19;
5360

5461
public DataSourceRegistry(MigratorProperties properties) {
5562
this.c7DataSource = createC7DataSource(properties);
@@ -176,5 +183,37 @@ protected DataSource selectMigratorDataSource() {
176183
// Prefer C8 datasource when configured for single-transaction atomicity
177184
return c8DataSource != null ? c8DataSource : c7DataSource;
178185
}
186+
187+
/**
188+
* Checks if the current database is Oracle 19 or below.
189+
* Oracle 19 requires special handling for insertion.
190+
* Caches the result after first check.
191+
*/
192+
public boolean isOracle19() {
193+
if (isOracle19 != null) {
194+
return isOracle19;
195+
}
196+
197+
try {
198+
DataSource dataSource = getC8DataSource().get();
199+
try (var conn = dataSource.getConnection()) {
200+
var meta = conn.getMetaData();
201+
String productName = meta.getDatabaseProductName();
202+
203+
if (productName != null && productName.toLowerCase().contains("oracle")) {
204+
int majorVersion = meta.getDatabaseMajorVersion();
205+
isOracle19 = majorVersion <= 19;
206+
LOGGER.debug("Detected Oracle database version: {}, treating as Oracle 19: {}", majorVersion, isOracle19);
207+
return isOracle19;
208+
}
209+
}
210+
} catch (SQLException e) {
211+
LOGGER.warn("Failed to detect database version, assuming not Oracle 19", e);
212+
}
213+
214+
isOracle19 = false;
215+
return false;
216+
}
217+
179218
}
180219

data-migrator/core/src/main/java/io/camunda/migration/data/impl/clients/C8Client.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88
package io.camunda.migration.data.impl.clients;
99

10+
import static io.camunda.db.rdbms.write.domain.DecisionInstanceDbModel.*;
11+
import static io.camunda.db.rdbms.write.domain.ProcessInstanceDbModel.*;
1012
import static io.camunda.migration.data.constants.MigratorConstants.C8_DEFAULT_TENANT;
1113
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_ACTIVATE_JOBS;
1214
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_CREATE_PROCESS_INSTANCE;
@@ -35,7 +37,6 @@
3537
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_SEARCH_DECISION_INSTANCES;
3638
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_SEARCH_FLOW_NODE_INSTANCES;
3739
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_SEARCH_PROCESS_DEFINITIONS;
38-
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_SEARCH_PROCESS_INSTANCE;
3940
import static io.camunda.migration.data.impl.logging.C8ClientLogs.FAILED_TO_SEARCH_USER_TASKS;
4041
import static io.camunda.migration.data.impl.util.ConverterUtil.getTenantId;
4142
import static io.camunda.migration.data.impl.util.ExceptionUtils.callApi;
@@ -59,7 +60,6 @@
5960
import io.camunda.db.rdbms.read.domain.DecisionInstanceDbQuery;
6061
import io.camunda.db.rdbms.read.domain.FlowNodeInstanceDbQuery;
6162
import io.camunda.db.rdbms.read.domain.ProcessDefinitionDbQuery;
62-
import io.camunda.db.rdbms.read.domain.ProcessInstanceDbQuery;
6363
import io.camunda.db.rdbms.read.domain.UserTaskDbQuery;
6464
import io.camunda.db.rdbms.sql.AuditLogMapper;
6565
import io.camunda.db.rdbms.sql.DecisionDefinitionMapper;
@@ -85,6 +85,7 @@
8585
import io.camunda.db.rdbms.write.domain.VariableDbModel;
8686
import io.camunda.db.rdbms.write.queue.BatchInsertDto;
8787
import io.camunda.migration.data.config.property.MigratorProperties;
88+
import io.camunda.migration.data.impl.DataSourceRegistry;
8889
import io.camunda.migration.data.impl.identity.C8Authorization;
8990
import io.camunda.migration.data.impl.model.FlowNodeActivation;
9091
import io.camunda.search.entities.DecisionDefinitionEntity;
@@ -113,6 +114,9 @@ public class C8Client {
113114
@Autowired
114115
protected CamundaClient camundaClient;
115116

117+
@Autowired
118+
protected DataSourceRegistry dataSourceRegistry;
119+
116120
// MyBatis mappers for history migration
117121
// These are optional because they're only available when C8 data source is configured
118122
@Autowired(required = false)
@@ -267,7 +271,17 @@ public void insertProcessInstance(ProcessInstanceDbModel dbModel) {
267271
* Inserts Process Instance tags into the database.
268272
*/
269273
public void insertProcessInstanceTags(ProcessInstanceDbModel dbModel) {
270-
callApi(() -> processInstanceMapper.insertTags(dbModel), FAILED_TO_INSERT_PROCESS_INSTANCE);
274+
if (dataSourceRegistry.isOracle19()) {
275+
dbModel.tags().forEach(tag -> callApi(() -> {
276+
var finalTag = new ProcessInstanceDbModelBuilder()
277+
.processInstanceKey(dbModel.processInstanceKey())
278+
.tags(Set.of(tag))
279+
.build();
280+
processInstanceMapper.insertTags(finalTag);
281+
}, FAILED_TO_INSERT_PROCESS_INSTANCE));
282+
} else {
283+
callApi(() -> processInstanceMapper.insertTags(dbModel), FAILED_TO_INSERT_PROCESS_INSTANCE);
284+
}
271285
}
272286

273287
/**
@@ -303,12 +317,33 @@ public List<DecisionDefinitionEntity> searchDecisionDefinitions(DecisionDefiniti
303317
*/
304318
public void insertDecisionInstance(DecisionInstanceDbModel dbModel) {
305319
callApi(() -> decisionInstanceMapper.insert(dbModel), FAILED_TO_INSERT_DECISION_INSTANCE);
320+
306321
if (!dbModel.evaluatedInputs().isEmpty()) {
307-
callApi(() -> decisionInstanceMapper.insertInput(dbModel), FAILED_TO_INSERT_DECISION_INSTANCE_INPUT);
322+
if (dataSourceRegistry.isOracle19()) {
323+
dbModel.evaluatedInputs().forEach(input -> callApi(() -> {
324+
var finalInput = new Builder()
325+
.decisionInstanceId(dbModel.decisionInstanceId())
326+
.evaluatedInputs(List.of(input))
327+
.build();
328+
decisionInstanceMapper.insertInput(finalInput);
329+
}, FAILED_TO_INSERT_DECISION_INSTANCE_INPUT));
330+
} else {
331+
decisionInstanceMapper.insertInput(dbModel);
332+
}
308333
}
309334

310335
if (!dbModel.evaluatedOutputs().isEmpty()) {
311-
callApi(() -> decisionInstanceMapper.insertOutput(dbModel), FAILED_TO_INSERT_DECISION_INSTANCE_OUTPUT);
336+
if (dataSourceRegistry.isOracle19()) {
337+
dbModel.evaluatedOutputs().forEach(output -> callApi(() -> {
338+
var finalOutput = new Builder()
339+
.decisionInstanceId(dbModel.decisionInstanceId())
340+
.evaluatedOutputs(List.of(output))
341+
.build();
342+
decisionInstanceMapper.insertOutput(finalOutput);
343+
}, FAILED_TO_INSERT_DECISION_INSTANCE_OUTPUT));
344+
} else {
345+
decisionInstanceMapper.insertOutput(dbModel);
346+
}
312347
}
313348
}
314349

@@ -330,7 +365,7 @@ public void insertIncident(IncidentDbModel dbModel) {
330365
* Inserts a Variable into the database.
331366
*/
332367
public void insertVariable(VariableDbModel dbModel) {
333-
callApi(() -> variableMapper.insert(new BatchInsertDto(List.of(dbModel))), FAILED_TO_INSERT_VARIABLE);
368+
callApi(() -> variableMapper.insert(new BatchInsertDto<>(List.of(dbModel))), FAILED_TO_INSERT_VARIABLE);
334369
}
335370

336371
/**
@@ -344,7 +379,17 @@ public void insertUserTask(UserTaskDbModel dbModel) {
344379
* Inserts User Task tags into the database.
345380
*/
346381
public void insertUserTaskTags(UserTaskDbModel dbModel) {
347-
callApi(() -> userTaskMapper.insertTags(dbModel), FAILED_TO_INSERT_USER_TASK);
382+
if (!dataSourceRegistry.isOracle19()) {
383+
callApi(() -> userTaskMapper.insertTags(dbModel), FAILED_TO_INSERT_USER_TASK);
384+
} else {
385+
dbModel.tags().forEach(tag -> callApi(() -> {
386+
var finalTag = new UserTaskDbModel.Builder()
387+
.userTaskKey(dbModel.userTaskKey())
388+
.tags(Set.of(tag))
389+
.build();
390+
userTaskMapper.insertTags(finalTag);
391+
}, FAILED_TO_INSERT_USER_TASK));
392+
}
348393
}
349394

350395
/**

0 commit comments

Comments
 (0)