diff --git a/core/src/main/java/com/scalar/db/common/error/CoreError.java b/core/src/main/java/com/scalar/db/common/error/CoreError.java index 7c5a5fd1d4..b6e450107e 100644 --- a/core/src/main/java/com/scalar/db/common/error/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/error/CoreError.java @@ -923,6 +923,14 @@ public enum CoreError implements ScalarDbError { "Some scanners were not closed. All scanners must be closed before preparing the transaction.", "", ""), + DATA_LOADER_INVALID_DATA_CHUNK_SIZE( + Category.USER_ERROR, "0207", "Data chunk size must be greater than 0", "", ""), + DATA_LOADER_INVALID_TRANSACTION_SIZE( + Category.USER_ERROR, "0208", "Transaction size must be greater than 0", "", ""), + DATA_LOADER_INVALID_MAX_THREADS( + Category.USER_ERROR, "0209", "Number of max threads must be greater than 0", "", ""), + DATA_LOADER_INVALID_DATA_CHUNK_QUEUE_SIZE( + Category.USER_ERROR, "0210", "Data chunk queue size must be greater than 0", "", ""), // // Errors for the concurrency error category diff --git a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataexport/ExportCommand.java b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataexport/ExportCommand.java index fdedbeef2c..664bb079e8 100755 --- a/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataexport/ExportCommand.java +++ b/data-loader/cli/src/main/java/com/scalar/db/dataloader/cli/command/dataexport/ExportCommand.java @@ -1,5 +1,6 @@ package com.scalar.db.dataloader.cli.command.dataexport; +import static com.scalar.db.dataloader.cli.util.CommandLineInputUtils.validatePositiveValue; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; @@ -56,6 +57,10 @@ public Integer call() throws Exception { try { validateOutputDirectory(); FileUtils.validateFilePath(scalarDbPropertiesFilePath); + validatePositiveValue( + spec.commandLine(), dataChunkSize, CoreError.DATA_LOADER_INVALID_DATA_CHUNK_SIZE); + validatePositiveValue( + spec.commandLine(), maxThreads, CoreError.DATA_LOADER_INVALID_MAX_THREADS); StorageFactory storageFactory = StorageFactory.create(scalarDbPropertiesFilePath); TableMetadataService metaDataService = 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 604adc0586..a505a42ade 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 @@ -1,5 +1,7 @@ package com.scalar.db.dataloader.cli.command.dataimport; +import static com.scalar.db.dataloader.cli.util.CommandLineInputUtils.validatePositiveValue; + import com.fasterxml.jackson.databind.ObjectMapper; import com.scalar.db.api.DistributedStorageAdmin; import com.scalar.db.api.TableMetadata; @@ -52,6 +54,16 @@ public class ImportCommand extends ImportCommandOptions implements Callable importCommand.call()); } diff --git a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/util/CommandLineInputUtilsTest.java b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/util/CommandLineInputUtilsTest.java index 7faebef61a..49e735b522 100644 --- a/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/util/CommandLineInputUtilsTest.java +++ b/data-loader/cli/src/test/java/com/scalar/db/dataloader/cli/util/CommandLineInputUtilsTest.java @@ -1,13 +1,16 @@ package com.scalar.db.dataloader.cli.util; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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 com.scalar.db.common.error.CoreError; import java.util.Map; import org.junit.jupiter.api.Test; +import picocli.CommandLine; class CommandLineInputUtilsTest { @@ -97,4 +100,95 @@ void splitByDelimiter_nullDelimiter_shouldThrowException() { .getMessage() .contains(CoreError.DATA_LOADER_SPLIT_INPUT_DELIMITER_NULL.buildMessage())); } + + @Test + public void validatePositiveValue_positiveValue_shouldNotThrowException() { + // Arrange + CommandLine commandLine = mock(CommandLine.class); + int positiveValue = 5; + + // Act & Assert - No exception should be thrown + assertDoesNotThrow( + () -> + CommandLineInputUtils.validatePositiveValue( + commandLine, positiveValue, CoreError.DATA_LOADER_INVALID_DATA_CHUNK_SIZE)); + } + + @Test + public void validatePositiveValue_one_shouldNotThrowException() { + // Arrange + CommandLine commandLine = mock(CommandLine.class); + int minimumPositiveValue = 1; + + // Act & Assert - No exception should be thrown + assertDoesNotThrow( + () -> + CommandLineInputUtils.validatePositiveValue( + commandLine, minimumPositiveValue, CoreError.DATA_LOADER_INVALID_DATA_CHUNK_SIZE)); + } + + @Test + public void validatePositiveValue_zero_shouldThrowException() { + // Arrange + CommandLine commandLine = mock(CommandLine.class); + int zeroValue = 0; + CoreError error = CoreError.DATA_LOADER_INVALID_DATA_CHUNK_SIZE; + + // Act & Assert + CommandLine.ParameterException exception = + assertThrows( + CommandLine.ParameterException.class, + () -> CommandLineInputUtils.validatePositiveValue(commandLine, zeroValue, error)); + + // Verify the exception message contains the error message + assertTrue(exception.getMessage().contains(error.buildMessage())); + } + + @Test + public void validatePositiveValue_negativeValue_shouldThrowException() { + // Arrange + CommandLine commandLine = mock(CommandLine.class); + int negativeValue = -5; + CoreError error = CoreError.DATA_LOADER_INVALID_TRANSACTION_SIZE; + + // Act & Assert + CommandLine.ParameterException exception = + assertThrows( + CommandLine.ParameterException.class, + () -> CommandLineInputUtils.validatePositiveValue(commandLine, negativeValue, error)); + + // Verify the exception message contains the error message + assertTrue(exception.getMessage().contains(error.buildMessage())); + } + + @Test + public void validatePositiveValue_differentErrorTypes_shouldUseCorrectErrorMessage() { + // Arrange + CommandLine commandLine = mock(CommandLine.class); + int negativeValue = -1; + + // Act & Assert for DATA_LOADER_INVALID_MAX_THREADS + CommandLine.ParameterException exception1 = + assertThrows( + CommandLine.ParameterException.class, + () -> + CommandLineInputUtils.validatePositiveValue( + commandLine, negativeValue, CoreError.DATA_LOADER_INVALID_MAX_THREADS)); + assertTrue( + exception1.getMessage().contains(CoreError.DATA_LOADER_INVALID_MAX_THREADS.buildMessage())); + + // Act & Assert for DATA_LOADER_INVALID_DATA_CHUNK_QUEUE_SIZE + CommandLine.ParameterException exception2 = + assertThrows( + CommandLine.ParameterException.class, + () -> + CommandLineInputUtils.validatePositiveValue( + commandLine, + negativeValue, + CoreError.DATA_LOADER_INVALID_DATA_CHUNK_QUEUE_SIZE)); + assertTrue( + exception2 + .getMessage() + .contains(CoreError.DATA_LOADER_INVALID_DATA_CHUNK_QUEUE_SIZE.buildMessage())); + } }