Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection to ScalarDB and parse keys #1768

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
437266e
Add dataloader sub projects base code
ypeckstadt May 14, 2024
e2d9443
Fix formatting[skip ci]
ypeckstadt May 14, 2024
133e5c8
Use subproject group to avoid naming conflicts
ypeckstadt May 15, 2024
0c7bd42
Remove comments[skip ci]
ypeckstadt May 15, 2024
35b14ab
Merge branch 'master' into feat/dataloader-core-base
ypeckstadt May 15, 2024
131e192
Add export commaond options and CLI column key convertor[skip ci]
ypeckstadt May 16, 2024
f9c4657
Merge branch 'master' into feat/export-options
ypeckstadt May 16, 2024
8dec538
Revert ExportCommandOptions[skip ci]
ypeckstadt May 16, 2024
033ec39
Avoid wildcard static imports
ypeckstadt May 16, 2024
660873e
Add file and directory validation utils
ypeckstadt May 16, 2024
84597dd
Apply spotless fix
ypeckstadt May 16, 2024
8d25192
Add config and output file path validation
ypeckstadt May 16, 2024
12df520
Remove scalardb config file for now
ypeckstadt May 19, 2024
e6ab26a
Remove Lombok usage to avoid spotbugs warnings
ypeckstadt May 19, 2024
2f32001
Merge branch 'feat/export-options' into feat/dataloader/file-validation
ypeckstadt May 19, 2024
cef935d
Use individual field comparison in unit tests after removing Lombok
ypeckstadt May 19, 2024
bded8d7
Merge branch 'feat/export-options' into feat/dataloader/file-validation
ypeckstadt May 19, 2024
d669ef7
Fix unit tests
ypeckstadt May 19, 2024
16ddeed
Fix unit tests
ypeckstadt May 19, 2024
937a849
WIP
ypeckstadt May 19, 2024
78ca12d
WIP
ypeckstadt May 19, 2024
1dffddb
Merge branch 'master' into feat/dataloader/file-validation
ypeckstadt May 21, 2024
c5df1cc
Apply spotless formatting
ypeckstadt May 21, 2024
444a5eb
Remove unused class
ypeckstadt May 21, 2024
c7deb11
Allow empty output-file path
ypeckstadt May 21, 2024
2701093
Merge branch 'feat/dataloader/file-validation' into feat/data-loader/…
ypeckstadt May 21, 2024
a6cac67
Apply PR suggestions
ypeckstadt May 22, 2024
5e879b6
Merge branch 'feat/dataloader/file-validation' into feat/data-loader/…
ypeckstadt May 22, 2024
1815943
Move error messages to ScalarDB Core error messages
ypeckstadt May 24, 2024
cec76eb
Merge branch 'master' into feat/dataloader/file-validation
ypeckstadt May 24, 2024
ce96cb4
Merge branch 'feat/dataloader/file-validation' into feat/data-loader/…
ypeckstadt May 24, 2024
33e25fb
Fix spotbugs
ypeckstadt May 25, 2024
8013847
Remove unused class
ypeckstadt May 25, 2024
fffeaf1
Remove unused inline variables
ypeckstadt May 25, 2024
630cf95
Merge branch 'master' into feat/data-loader/scalardb-connection
ypeckstadt May 25, 2024
2e38741
Add mockito and core project in Gradle files
ypeckstadt May 26, 2024
3797510
WIP
ypeckstadt May 26, 2024
637b474
Move ColumnKeyValue to data loader core project
ypeckstadt May 26, 2024
a113450
Merge branch 'feat/data-loader/columnkeyvalue' into feat/data-loader/…
ypeckstadt May 26, 2024
89028b4
Add dataloader core key and column utils
ypeckstadt May 26, 2024
cee97dc
Add TableMetadataService and utils
ypeckstadt May 26, 2024
7702a21
Merge branch 'feat/data-loader/tablemetadata-service' into feat/data-…
ypeckstadt May 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/src/main/java/com/scalar/db/common/error/CoreError.java
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,16 @@ public enum CoreError implements ScalarDbError {
"Invalid file extension: %s. Allowed extensions are: %s",
"",
""),
DATA_LOADER_INVALID_COLUMN_KEY_PARSING_FAILED(
Category.USER_ERROR, "0136", "Invalid key: Column %s does not exist in the table.", "", ""),
DATA_LOADER_INVALID_VALUE_KEY_PARSING_FAILED(
Category.USER_ERROR, "0137", "Parsing of key value %s failed. Details:%s.", "", ""),
DATA_LOADER_INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE(
Category.USER_ERROR, "0138", "Invalid base64 encoding for blob value for column %s", "", ""),
DATA_LOADER_INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE(
Category.USER_ERROR, "0139", "Invalid number specified for column %s", "", ""),
DATA_LOADER_MISSING_NAMESPACE_OR_TABLE(
Category.USER_ERROR, "0140", "Missing namespace or table: %s, %s.", "", ""),

//
// Errors for the concurrency error category
Expand Down
5 changes: 5 additions & 0 deletions data-loader/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@ subprojects {
// Apache Commons
implementation("org.apache.commons:commons-lang3:${apacheCommonsLangVersion}")
implementation("commons-io:commons-io:${apacheCommonsIoVersion}")

// Mockito
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
testImplementation "org.mockito:mockito-junit-jupiter:${mockitoVersion}"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.scalar.db.dataloader.cli.command;

import com.scalar.db.dataloader.core.ColumnKeyValue;
import picocli.CommandLine;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.scalar.db.dataloader.cli.command.dataexport;

import com.scalar.db.api.TableMetadata;
import com.scalar.db.common.error.CoreError;
import com.scalar.db.dataloader.cli.exception.DirectoryValidationException;
import com.scalar.db.dataloader.cli.exception.InvalidFileExtensionException;
import com.scalar.db.dataloader.cli.util.DirectoryUtils;
import com.scalar.db.dataloader.core.tablemetadata.TableMetadataService;
import com.scalar.db.dataloader.core.util.KeyUtils;
import com.scalar.db.service.StorageFactory;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
Expand All @@ -25,6 +30,13 @@ public class ExportCommand extends ExportCommandOptions implements Callable<Inte
@Override
public Integer call() throws Exception {
validateOutputDirectory(outputFilePath);
StorageFactory storageFactory = createStorageFactory(configFilePath);
TableMetadataService metadataService = createTableMetadataService(storageFactory);
TableMetadata tableMetadata = metadataService.getTableMetadata(namespace, tableName);

KeyUtils.parseKeyValue(partitionKeyValue, tableMetadata);
KeyUtils.parseKeyValue(scanStartKeyValue, tableMetadata);
KeyUtils.parseKeyValue(scanEndKeyValue, tableMetadata);
return 0;
}

Expand Down Expand Up @@ -67,4 +79,12 @@ private void validateFileExtension(String filename) throws InvalidFileExtensionE
extension, String.join(", ", ALLOWED_EXTENSIONS)));
}
}

protected StorageFactory createStorageFactory(String configFilePath) throws IOException {
return StorageFactory.create(configFilePath);
}

protected TableMetadataService createTableMetadataService(StorageFactory storageFactory) {
return new TableMetadataService(storageFactory);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,59 @@
package com.scalar.db.dataloader.cli.command.dataexport;

import com.scalar.db.dataloader.core.ColumnKeyValue;
import picocli.CommandLine;

/** A class to represent the command options for the export command. */
public class ExportCommandOptions {

protected static final String DEFAULT_CONFIG_FILE_NAME = "scalardb.properties";

@CommandLine.Option(
names = {"--output-file", "-o"},
paramLabel = "<OUTPUT_FILE>",
description =
"Path and name of the output file for the exported data (default: <table_name>.<format>)")
protected String outputFilePath;

@CommandLine.Option(
names = {"--config-file", "-c"},
paramLabel = "<CONFIG_FILE>",
description = "Path to the ScalarDB configuration file (default: scalardb.properties)",
defaultValue = DEFAULT_CONFIG_FILE_NAME)
protected String configFilePath;

@CommandLine.Option(
names = {"--namespace", "-ns"},
paramLabel = "<NAMESPACE>",
required = true,
description = "ScalarDB namespace containing the table to export data from")
protected String namespace;

@CommandLine.Option(
names = {"--table", "-t"},
paramLabel = "<TABLE_NAME>",
required = true,
description = "Name of the ScalarDB table to export data from")
protected String tableName;

@CommandLine.Option(
names = {"--partition-key", "-pk"},
paramLabel = "<PARTITION_KEY=VALUE>",
description =
"ScalarDB partition key and value in the format 'key=value'. If not provided, a full table scan is executed.")
protected ColumnKeyValue partitionKeyValue;

@CommandLine.Option(
names = {"--scan-start-key", "-sk"},
paramLabel = "<START_KEY=VALUE>",
description =
"Clustering key and value to mark the start of the scan in format 'columnName=value'")
protected ColumnKeyValue scanStartKeyValue;

@CommandLine.Option(
names = {"--scan-end-key", "-ek"},
paramLabel = "<END_KEY=VALUE>",
description =
"Clustering key and value to mark the end of the scan in format 'columnName=value'")
protected ColumnKeyValue scanEndKeyValue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.scalar.db.dataloader.core.ColumnKeyValue;
import org.junit.jupiter.api.Test;

class ColumnKeyValueConverterTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.scalar.db.dataloader.cli.command.dataexport;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;

import com.scalar.db.api.DistributedStorageAdmin;
import com.scalar.db.api.TableMetadata;
import com.scalar.db.dataloader.cli.exception.InvalidFileExtensionException;
import com.scalar.db.dataloader.core.tablemetadata.TableMetadataException;
import com.scalar.db.dataloader.core.tablemetadata.TableMetadataService;
import com.scalar.db.service.StorageFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -12,6 +19,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
Expand All @@ -20,19 +30,41 @@ class ExportCommandTest {

private static final Logger LOGGER = LoggerFactory.getLogger(ExportCommandTest.class);
@TempDir Path tempDir;
@Mock StorageFactory storageFactory;
@Mock TableMetadataService tableMetadataService;
@Mock DistributedStorageAdmin storageAdmin;
@Mock TableMetadata tableMetadata;

private ExportCommand exportCommand;
@InjectMocks private ExportCommand exportCommand;

private AutoCloseable closeable;

@BeforeEach
void setUp() {
exportCommand = new ExportCommand();
void setUp() throws TableMetadataException {
closeable = MockitoAnnotations.openMocks(this);
exportCommand =
new ExportCommand() {
@Override
protected StorageFactory createStorageFactory(String configFilePath) {
return storageFactory;
}

@Override
protected TableMetadataService createTableMetadataService(StorageFactory storageFactory) {
return tableMetadataService;
}
};

CommandLine cmd = new CommandLine(exportCommand);
exportCommand.spec = cmd.getCommandSpec();
doReturn(storageAdmin).when(storageFactory).getStorageAdmin();
doReturn(tableMetadata).when(tableMetadataService).getTableMetadata(anyString(), anyString());
}

@AfterEach
public void cleanup() throws IOException {
public void cleanup() throws Exception {
cleanUpTempDir();
closeable.close();
}

@Test
Expand Down Expand Up @@ -92,6 +124,7 @@ void call_WithValidOutputFileWithoutDirectory_ShouldReturnZero() throws Exceptio
Files.createFile(configFile);

exportCommand.outputFilePath = "output.csv";
exportCommand.configFilePath = ExportCommandOptions.DEFAULT_CONFIG_FILE_NAME;

assertEquals(0, exportCommand.call());
}
Expand Down
3 changes: 3 additions & 0 deletions data-loader/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ plugins {
archivesBaseName = "scalardb-data-loader-core"

dependencies {
// ScalarDB core
implementation project(':core')

// for SpotBugs
compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}"
testCompileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.scalar.db.dataloader.cli.command;
package com.scalar.db.dataloader.core;

/** Represents a key-value pair for a column and its corresponding value. */
public class ColumnKeyValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.scalar.db.dataloader.core.exception;

/** Exception thrown when an error occurs while trying to encode or decode base64 values. */
public class Base64Exception extends Exception {

/**
* Class constructor
*
* @param message Exception message
*/
public Base64Exception(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.scalar.db.dataloader.core.exception;

public class KeyParsingException extends Exception {

public KeyParsingException(String message) {
super(message);
}

public KeyParsingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.scalar.db.dataloader.core.tablemetadata;

/** A custom exception that encapsulates errors thrown by the TableMetaDataService */
public class TableMetadataException extends Exception {

/**
* Class constructor
*
* @param message error message
* @param cause reason for exception
*/
public TableMetadataException(String message, Throwable cause) {
super(message, cause);
}

/**
* Class constructor
*
* @param message error message
*/
public TableMetadataException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.scalar.db.dataloader.core.tablemetadata;

/** Represents the request for metadata for a single ScalarDB table */
public class TableMetadataRequest {

private final String namespace;
private final String tableName;

/**
* Class constructor
*
* @param namespace ScalarDB namespace
* @param tableName ScalarDB table name
*/
public TableMetadataRequest(String namespace, String tableName) {
this.namespace = namespace;
this.tableName = tableName;
}

public String getNamespace() {
return namespace;
}

public String getTableName() {
return tableName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.scalar.db.dataloader.core.tablemetadata;

import com.scalar.db.api.TableMetadata;
import com.scalar.db.common.error.CoreError;
import com.scalar.db.dataloader.core.util.TableMetadataUtils;
import com.scalar.db.exception.storage.ExecutionException;
import com.scalar.db.service.StorageFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* A service class that provides methods to get TableMetadata for a given namespace and table name.
*/
public class TableMetadataService {

private final StorageFactory storageFactory;

/**
* Class constructor
*
* @param storageFactory a distributed storage admin
*/
public TableMetadataService(StorageFactory storageFactory) {
this.storageFactory = storageFactory;
}

/**
* Returns the TableMetadata for the given namespace and table name.
*
* @param namespace ScalarDb namespace
* @param tableName ScalarDb table name
* @return TableMetadata
* @throws TableMetadataException if the namespace or table is missing
*/
public TableMetadata getTableMetadata(String namespace, String tableName)
throws TableMetadataException {
try {
TableMetadata tableMetadata =
storageFactory.getStorageAdmin().getTableMetadata(namespace, tableName);
if (tableMetadata == null) {
throw new TableMetadataException(
CoreError.DATA_LOADER_MISSING_NAMESPACE_OR_TABLE.buildMessage(namespace, tableName));
}
return tableMetadata;
} catch (ExecutionException e) {
throw new TableMetadataException(
CoreError.DATA_LOADER_MISSING_NAMESPACE_OR_TABLE.buildMessage(namespace, tableName));
}
}

/**
* Returns the TableMetadata for the given list of TableMetadataRequest.
*
* @param requests List of TableMetadataRequest
* @return Map of TableMetadata
* @throws TableMetadataException if the namespace or table is missing
*/
public Map<String, TableMetadata> getTableMetadata(Collection<TableMetadataRequest> requests)
throws TableMetadataException {
Map<String, TableMetadata> metadataMap = new HashMap<>();

for (TableMetadataRequest request : requests) {
String namespace = request.getNamespace();
String tableName = request.getTableName();
TableMetadata tableMetadata = getTableMetadata(namespace, tableName);
String key = TableMetadataUtils.getTableLookupKey(namespace, tableName);
metadataMap.put(key, tableMetadata);
}

return metadataMap;
}
}
Loading
Loading