From cb6adbbf970450e5faba9c0b4dbf95f8cbab81b3 Mon Sep 17 00:00:00 2001 From: Pham Ba Thong Date: Wed, 19 Nov 2025 18:34:28 +0900 Subject: [PATCH 1/3] Add early validation for transaction mode compatibility in the data-loader import command --- .../cli/command/dataimport/ImportCommand.java | 92 +++++++++++++++++-- .../command/dataimport/ImportCommandTest.java | 66 +++++++++++++ .../db/dataloader/core/DataLoaderError.java | 6 ++ 3 files changed, 155 insertions(+), 9 deletions(-) diff --git a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java index 7de29db0cd..774c9bf4d5 100755 --- a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java +++ b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java @@ -4,10 +4,13 @@ import static com.scalar.db.dataloader.cli.util.CommandLineInputUtils.validatePositiveValue; import com.fasterxml.jackson.databind.ObjectMapper; +import com.scalar.db.api.DistributedTransaction; import com.scalar.db.api.DistributedTransactionAdmin; import com.scalar.db.api.TableMetadata; +import com.scalar.db.common.CoreError; import com.scalar.db.dataloader.core.DataLoaderError; import com.scalar.db.dataloader.core.FileFormat; +import com.scalar.db.dataloader.core.ScalarDbMode; import com.scalar.db.dataloader.core.dataimport.ImportManager; import com.scalar.db.dataloader.core.dataimport.ImportOptions; import com.scalar.db.dataloader.core.dataimport.controlfile.ControlFile; @@ -75,12 +78,24 @@ public Integer call() throws Exception { .prettyPrint(prettyPrint) .build(); LogWriterFactory logWriterFactory = createLogWriterFactory(config); + File configFile = new File(configFilePath); + TransactionFactory transactionFactory = TransactionFactory.create(configFile); + + // Validate transaction mode configuration before proceeding + validateTransactionMode(transactionFactory); + Map tableMetadataMap = - createTableMetadataMap(controlFile, namespace, tableName); + createTableMetadataMap(controlFile, namespace, tableName, transactionFactory); try (BufferedReader reader = Files.newBufferedReader(Paths.get(sourceFilePath), Charset.defaultCharset())) { ImportManager importManager = - createImportManager(importOptions, tableMetadataMap, reader, logWriterFactory, config); + createImportManager( + importOptions, + tableMetadataMap, + reader, + logWriterFactory, + config, + transactionFactory); importManager.startImport(); } return 0; @@ -101,14 +116,16 @@ private LogWriterFactory createLogWriterFactory(ImportLoggerConfig config) { * @param controlFile control file * @param namespace Namespace * @param tableName Single table name + * @param transactionFactory transaction factory to use * @return {@code Map} a table metadata map * @throws ParameterException if one of the argument values is wrong */ private Map createTableMetadataMap( - ControlFile controlFile, String namespace, String tableName) - throws IOException, TableMetadataException { - File configFile = new File(configFilePath); - TransactionFactory transactionFactory = TransactionFactory.create(configFile); + ControlFile controlFile, + String namespace, + String tableName, + TransactionFactory transactionFactory) + throws TableMetadataException { try (DistributedTransactionAdmin transactionAdmin = transactionFactory.getTransactionAdmin()) { TableMetadataService tableMetadataService = new TableMetadataService(transactionAdmin); Map tableMetadataMap = new HashMap<>(); @@ -135,6 +152,7 @@ private Map createTableMetadataMap( * @param reader buffered reader with source data * @param logWriterFactory log writer factory object * @param config import logging config + * @param transactionFactory transaction factory to use * @return ImportManager object */ private ImportManager createImportManager( @@ -142,9 +160,9 @@ private ImportManager createImportManager( Map tableMetadataMap, BufferedReader reader, LogWriterFactory logWriterFactory, - ImportLoggerConfig config) + ImportLoggerConfig config, + TransactionFactory transactionFactory) throws IOException { - File configFile = new File(configFilePath); ImportProcessorFactory importProcessorFactory = new DefaultImportProcessorFactory(); ImportManager importManager = new ImportManager( @@ -153,7 +171,7 @@ private ImportManager createImportManager( importOptions, importProcessorFactory, scalarDbMode, - TransactionFactory.create(configFile).getTransactionManager()); + transactionFactory.getTransactionManager()); if (importOptions.getLogMode().equals(LogMode.SPLIT_BY_DATA_CHUNK)) { importManager.addListener(new SplitByDataChunkImportLogger(config, logWriterFactory)); } else { @@ -236,6 +254,62 @@ private void validateLogDirectory(String logDirectory) throws ParameterException } } + /** + * Validate transaction mode configuration by attempting to start and abort a transaction + * + * @param transactionFactory transaction factory to test + * @throws ParameterException if transaction mode is incompatible with the configured transaction + * manager + */ + void validateTransactionMode(TransactionFactory transactionFactory) { + // Only validate when in TRANSACTION mode + if (scalarDbMode != ScalarDbMode.TRANSACTION) { + return; + } + + DistributedTransaction transaction = null; + try { + // Try to start a read only transaction to verify the transaction manager is properly + // configured + transaction = transactionFactory.getTransactionManager().startReadOnly(); + } catch (Exception e) { + // Check for specific error about beginning transaction not allowed + if (e.getMessage() != null + && e.getMessage() + .contains( + CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED + .buildCode())) { + throw new ParameterException( + spec.commandLine(), + DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage( + "The current configuration does not support TRANSACTION mode. " + + "Please try with STORAGE mode or check your ScalarDB configuration. " + + "Error: " + + e.getClass().getSimpleName() + + " - " + + e.getMessage())); + } + + // Other exceptions - configuration or runtime error + throw new ParameterException( + spec.commandLine(), + DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage( + "Failed to validate transaction mode compatibility. Error: " + + e.getClass().getSimpleName() + + " - " + + e.getMessage())); + } finally { + // Ensure transaction is aborted + if (transaction != null) { + try { + transaction.abort(); + } catch (Exception ignored) { + // Ignore errors during cleanup + } + } + } + } + /** * Generate control file from a valid control file path * diff --git a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java index 76fcf94cb3..4c29ecdf69 100755 --- a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java +++ b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java @@ -3,9 +3,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.common.CoreError; import com.scalar.db.dataloader.core.FileFormat; +import com.scalar.db.dataloader.core.ScalarDbMode; import com.scalar.db.dataloader.core.dataimport.ImportMode; +import com.scalar.db.exception.transaction.TransactionException; +import com.scalar.db.service.TransactionFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -279,4 +286,63 @@ void call_withoutMaxThreads_shouldDefaultToAvailableProcessors() { // Verify it was set to available processors assertEquals(Runtime.getRuntime().availableProcessors(), command.maxThreads); } + + @Test + void validateTransactionMode_withInvalidConfig_shouldThrowException() throws Exception { + // Arrange - Mock TransactionFactory to throw exception with error + TransactionFactory mockFactory = mock(TransactionFactory.class); + DistributedTransactionManager mockManager = mock(DistributedTransactionManager.class); + + when(mockFactory.getTransactionManager()).thenReturn(mockManager); + when(mockManager.startReadOnly()) + .thenThrow( + new TransactionException( + CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED + .buildMessage(), + null)); + + ImportCommand command = new ImportCommand(); + CommandLine cmd = new CommandLine(command); + command.spec = cmd.getCommandSpec(); + command.scalarDbMode = ScalarDbMode.TRANSACTION; + + // Act & Assert + CommandLine.ParameterException thrown = + assertThrows( + CommandLine.ParameterException.class, + () -> command.validateTransactionMode(mockFactory)); + + assertTrue(thrown.getMessage().contains("does not support TRANSACTION mode")); + assertTrue( + thrown + .getMessage() + .contains( + CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED + .buildCode())); + } + + @Test + void validateTransactionMode_withOtherException_shouldThrowException() throws Exception { + // Arrange - Mock TransactionFactory to throw a different exception + TransactionFactory mockFactory = mock(TransactionFactory.class); + DistributedTransactionManager mockManager = mock(DistributedTransactionManager.class); + + when(mockFactory.getTransactionManager()).thenReturn(mockManager); + when(mockManager.startReadOnly()).thenThrow(new RuntimeException("Connection failed")); + + ImportCommand command = new ImportCommand(); + CommandLine cmd = new CommandLine(command); + command.spec = cmd.getCommandSpec(); + command.scalarDbMode = ScalarDbMode.TRANSACTION; + + // Act & Assert + CommandLine.ParameterException thrown = + assertThrows( + CommandLine.ParameterException.class, + () -> command.validateTransactionMode(mockFactory)); + + assertTrue(thrown.getMessage().contains("Failed to validate transaction mode")); + assertTrue(thrown.getMessage().contains("RuntimeException")); + assertTrue(thrown.getMessage().contains("Connection failed")); + } } diff --git a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java index 605da3bc94..a3d1703973 100644 --- a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java +++ b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java @@ -216,6 +216,12 @@ public enum DataLoaderError implements ScalarDbError { "Cannot specify both deprecated option '%s' and new option '%s'. Please use only '%s'", "", ""), + INVALID_TRANSACTION_MODE( + Category.USER_ERROR, + "0058", + "TRANSACTION mode is not compatible with the current configuration. %s", + "", + ""), // // Errors for the internal error category From fef61993612be6e232575a43c1d335856a3d1fa8 Mon Sep 17 00:00:00 2001 From: Pham Ba Thong Date: Wed, 19 Nov 2025 19:10:38 +0900 Subject: [PATCH 2/3] apply suggestions --- .../cli/command/dataimport/ImportCommand.java | 31 ++++++------------- .../command/dataimport/ImportCommandTest.java | 23 ++++---------- .../db/dataloader/core/DataLoaderError.java | 2 ++ 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java index 774c9bf4d5..e3d01d17cc 100755 --- a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java +++ b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java @@ -7,7 +7,6 @@ import com.scalar.db.api.DistributedTransaction; import com.scalar.db.api.DistributedTransactionAdmin; import com.scalar.db.api.TableMetadata; -import com.scalar.db.common.CoreError; import com.scalar.db.dataloader.core.DataLoaderError; import com.scalar.db.dataloader.core.FileFormat; import com.scalar.db.dataloader.core.ScalarDbMode; @@ -272,32 +271,22 @@ void validateTransactionMode(TransactionFactory transactionFactory) { // Try to start a read only transaction to verify the transaction manager is properly // configured transaction = transactionFactory.getTransactionManager().startReadOnly(); - } catch (Exception e) { - // Check for specific error about beginning transaction not allowed - if (e.getMessage() != null - && e.getMessage() - .contains( - CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED - .buildCode())) { - throw new ParameterException( - spec.commandLine(), - DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage( - "The current configuration does not support TRANSACTION mode. " - + "Please try with STORAGE mode or check your ScalarDB configuration. " - + "Error: " - + e.getClass().getSimpleName() - + " - " - + e.getMessage())); - } - - // Other exceptions - configuration or runtime error + } catch (UnsupportedOperationException e) { + // Transaction mode is not supported by the configured transaction manager throw new ParameterException( spec.commandLine(), DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage( - "Failed to validate transaction mode compatibility. Error: " + "Please try with STORAGE mode or check your ScalarDB configuration. " + + "Error: " + e.getClass().getSimpleName() + " - " + e.getMessage())); + } catch (Exception e) { + // Other exceptions - configuration or runtime error + throw new ParameterException( + spec.commandLine(), + DataLoaderError.TRANSACTION_MODE_VALIDATION_FAILED.buildMessage( + "Error: " + e.getClass().getSimpleName() + " - " + e.getMessage())); } finally { // Ensure transaction is aborted if (transaction != null) { diff --git a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java index 4c29ecdf69..6a53a12f6f 100755 --- a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java +++ b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java @@ -7,11 +7,9 @@ import static org.mockito.Mockito.when; import com.scalar.db.api.DistributedTransactionManager; -import com.scalar.db.common.CoreError; import com.scalar.db.dataloader.core.FileFormat; import com.scalar.db.dataloader.core.ScalarDbMode; import com.scalar.db.dataloader.core.dataimport.ImportMode; -import com.scalar.db.exception.transaction.TransactionException; import com.scalar.db.service.TransactionFactory; import java.io.IOException; import java.nio.file.Files; @@ -288,18 +286,14 @@ void call_withoutMaxThreads_shouldDefaultToAvailableProcessors() { } @Test - void validateTransactionMode_withInvalidConfig_shouldThrowException() throws Exception { - // Arrange - Mock TransactionFactory to throw exception with error + void validateTransactionMode_withUnsupportedOperation_shouldThrowException() throws Exception { + // Arrange - Mock TransactionFactory to throw UnsupportedOperationException TransactionFactory mockFactory = mock(TransactionFactory.class); DistributedTransactionManager mockManager = mock(DistributedTransactionManager.class); when(mockFactory.getTransactionManager()).thenReturn(mockManager); when(mockManager.startReadOnly()) - .thenThrow( - new TransactionException( - CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED - .buildMessage(), - null)); + .thenThrow(new UnsupportedOperationException("Transaction mode is not supported")); ImportCommand command = new ImportCommand(); CommandLine cmd = new CommandLine(command); @@ -312,13 +306,8 @@ void validateTransactionMode_withInvalidConfig_shouldThrowException() throws Exc CommandLine.ParameterException.class, () -> command.validateTransactionMode(mockFactory)); - assertTrue(thrown.getMessage().contains("does not support TRANSACTION mode")); - assertTrue( - thrown - .getMessage() - .contains( - CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED - .buildCode())); + assertTrue(thrown.getMessage().contains("TRANSACTION mode is not compatible")); + assertTrue(thrown.getMessage().contains("UnsupportedOperationException")); } @Test @@ -341,7 +330,7 @@ void validateTransactionMode_withOtherException_shouldThrowException() throws Ex CommandLine.ParameterException.class, () -> command.validateTransactionMode(mockFactory)); - assertTrue(thrown.getMessage().contains("Failed to validate transaction mode")); + assertTrue(thrown.getMessage().contains("Failed to validate TRANSACTION mode")); assertTrue(thrown.getMessage().contains("RuntimeException")); assertTrue(thrown.getMessage().contains("Connection failed")); } diff --git a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java index a3d1703973..c95615c86c 100644 --- a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java +++ b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java @@ -222,6 +222,8 @@ public enum DataLoaderError implements ScalarDbError { "TRANSACTION mode is not compatible with the current configuration. %s", "", ""), + TRANSACTION_MODE_VALIDATION_FAILED( + Category.USER_ERROR, "0059", "Failed to validate TRANSACTION mode. %s", "", ""), // // Errors for the internal error category From 947083ea4e0686458acabb4a32abd304e6f095f4 Mon Sep 17 00:00:00 2001 From: Pham Ba Thong Date: Thu, 20 Nov 2025 11:39:47 +0900 Subject: [PATCH 3/3] apply suggestions --- .../cli/command/dataimport/ImportCommand.java | 27 +++++++++---------- .../command/dataimport/ImportCommandTest.java | 2 -- .../db/dataloader/core/DataLoaderError.java | 4 +-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java index e3d01d17cc..f5aea46e46 100755 --- a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java +++ b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommand.java @@ -69,14 +69,14 @@ public Integer call() throws Exception { spec.commandLine(), dataChunkQueueSize, DataLoaderError.INVALID_DATA_CHUNK_QUEUE_SIZE); ControlFile controlFile = parseControlFileFromPath(controlFilePath).orElse(null); ImportOptions importOptions = createImportOptions(controlFile); - ImportLoggerConfig config = + ImportLoggerConfig importLoggerConfig = ImportLoggerConfig.builder() .logDirectoryPath(logDirectory) .isLogRawSourceRecordsEnabled(importOptions.isLogRawRecord()) .isLogSuccessRecordsEnabled(importOptions.isLogSuccessRecords()) .prettyPrint(prettyPrint) .build(); - LogWriterFactory logWriterFactory = createLogWriterFactory(config); + LogWriterFactory logWriterFactory = createLogWriterFactory(importLoggerConfig); File configFile = new File(configFilePath); TransactionFactory transactionFactory = TransactionFactory.create(configFile); @@ -92,8 +92,8 @@ public Integer call() throws Exception { importOptions, tableMetadataMap, reader, + importLoggerConfig, logWriterFactory, - config, transactionFactory); importManager.startImport(); } @@ -149,8 +149,8 @@ private Map createTableMetadataMap( * @param importOptions import options * @param tableMetadataMap table metadata map * @param reader buffered reader with source data + * @param importLoggerConfig import logging config * @param logWriterFactory log writer factory object - * @param config import logging config * @param transactionFactory transaction factory to use * @return ImportManager object */ @@ -158,8 +158,8 @@ private ImportManager createImportManager( ImportOptions importOptions, Map tableMetadataMap, BufferedReader reader, + ImportLoggerConfig importLoggerConfig, LogWriterFactory logWriterFactory, - ImportLoggerConfig config, TransactionFactory transactionFactory) throws IOException { ImportProcessorFactory importProcessorFactory = new DefaultImportProcessorFactory(); @@ -172,9 +172,10 @@ private ImportManager createImportManager( scalarDbMode, transactionFactory.getTransactionManager()); if (importOptions.getLogMode().equals(LogMode.SPLIT_BY_DATA_CHUNK)) { - importManager.addListener(new SplitByDataChunkImportLogger(config, logWriterFactory)); + importManager.addListener( + new SplitByDataChunkImportLogger(importLoggerConfig, logWriterFactory)); } else { - importManager.addListener(new SingleFileImportLogger(config, logWriterFactory)); + importManager.addListener(new SingleFileImportLogger(importLoggerConfig, logWriterFactory)); } return importManager; } @@ -275,18 +276,14 @@ void validateTransactionMode(TransactionFactory transactionFactory) { // Transaction mode is not supported by the configured transaction manager throw new ParameterException( spec.commandLine(), - DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage( - "Please try with STORAGE mode or check your ScalarDB configuration. " - + "Error: " - + e.getClass().getSimpleName() - + " - " - + e.getMessage())); + DataLoaderError.INVALID_TRANSACTION_MODE.buildMessage(e.getMessage()), + e); } catch (Exception e) { // Other exceptions - configuration or runtime error throw new ParameterException( spec.commandLine(), - DataLoaderError.TRANSACTION_MODE_VALIDATION_FAILED.buildMessage( - "Error: " + e.getClass().getSimpleName() + " - " + e.getMessage())); + DataLoaderError.TRANSACTION_MODE_VALIDATION_FAILED.buildMessage(e.getMessage()), + e); } finally { // Ensure transaction is aborted if (transaction != null) { diff --git a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java index 6a53a12f6f..cd81e1eef0 100755 --- a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java +++ b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/command/dataimport/ImportCommandTest.java @@ -307,7 +307,6 @@ void validateTransactionMode_withUnsupportedOperation_shouldThrowException() thr () -> command.validateTransactionMode(mockFactory)); assertTrue(thrown.getMessage().contains("TRANSACTION mode is not compatible")); - assertTrue(thrown.getMessage().contains("UnsupportedOperationException")); } @Test @@ -331,7 +330,6 @@ void validateTransactionMode_withOtherException_shouldThrowException() throws Ex () -> command.validateTransactionMode(mockFactory)); assertTrue(thrown.getMessage().contains("Failed to validate TRANSACTION mode")); - assertTrue(thrown.getMessage().contains("RuntimeException")); assertTrue(thrown.getMessage().contains("Connection failed")); } } diff --git a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java index c95615c86c..b69be6b7b5 100644 --- a/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java +++ b/data-loader/core/src/main/java/com/scalar/db/dataloader/core/DataLoaderError.java @@ -219,11 +219,11 @@ public enum DataLoaderError implements ScalarDbError { INVALID_TRANSACTION_MODE( Category.USER_ERROR, "0058", - "TRANSACTION mode is not compatible with the current configuration. %s", + "TRANSACTION mode is not compatible with the current configuration. Please try with STORAGE mode or check your ScalarDB configuration. Details: %s", "", ""), TRANSACTION_MODE_VALIDATION_FAILED( - Category.USER_ERROR, "0059", "Failed to validate TRANSACTION mode. %s", "", ""), + Category.USER_ERROR, "0059", "Failed to validate TRANSACTION mode. Details: %s", "", ""), // // Errors for the internal error category