Skip to content

Commit

Permalink
Manual Backport to branch(3.11) : Add data loader core key and column…
Browse files Browse the repository at this point in the history
… utils (#2394)

Co-authored-by: Peckstadt <peckstadt.yves@gmail.com>
  • Loading branch information
inv-jishnu and ypeckstadt authored Dec 11, 2024
1 parent b12f821 commit 6dbe9d4
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 1 deletion.
2 changes: 1 addition & 1 deletion data-loader/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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
@@ -0,0 +1,24 @@
package com.scalar.db.dataloader.core;

import lombok.Builder;
import lombok.Value;

/**
* Represents a column in a database table.
*
* <p>This class holds the metadata for a column, including the namespace (schema), table name, and
* the column name within the table.
*/
@Value
@Builder
public class ColumnInfo {

/** The namespace (schema) where the table is located. */
String namespace;

/** The name of the table where the column resides. */
String tableName;

/** The name of the column in the table. */
String columnName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.scalar.db.dataloader.core;

public class ErrorMessage {
public static final String INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE =
"Invalid number specified for column %s in table %s in namespace %s";
public static final String INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE =
"Invalid base64 encoding for blob value for column %s in table %s in namespace %s";
public static final String INVALID_COLUMN_NON_EXISTENT =
"Invalid key: Column %s does not exist in the table %s in namespace %s.";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.scalar.db.dataloader.core.exception;

/**
* An exception that is thrown when an error occurs while trying to create a ScalarDB column from a
* value.
*
* <p>This exception is typically used to indicate a problem with parsing or converting data into a
* format that can be used to create a column in ScalarDB.
*/
public class ColumnParsingException extends Exception {

/**
* Constructs a new {@code ColumnParsingException} with the specified detail message.
*
* @param message the detail message explaining the cause of the exception
*/
public ColumnParsingException(String message) {
super(message);
}

/**
* Constructs a new {@code ColumnParsingException} with the specified detail message and cause.
*
* @param message the detail message explaining the cause of the exception
* @param cause the cause of the exception (can be {@code null})
*/
public ColumnParsingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.scalar.db.dataloader.core.exception;

/**
* An exception that is thrown when an error occurs while trying to create a ScalarDB key from a
* value.
*
* <p>This exception is typically used to indicate a problem with parsing or converting data into a
* format that can be used to create a key in ScalarDB.
*/
public class KeyParsingException extends Exception {

/**
* Constructs a new {@code KeyParsingException} with the specified detail message.
*
* @param message the detail message explaining the cause of the exception
*/
public KeyParsingException(String message) {
super(message);
}

/**
* Constructs a new {@code KeyParsingException} with the specified detail message and cause.
*
* @param message the detail message explaining the cause of the exception
* @param cause the cause of the exception (can be {@code null})
*/
public KeyParsingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.scalar.db.dataloader.core.util;

import static com.scalar.db.dataloader.core.ErrorMessage.INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE;
import static com.scalar.db.dataloader.core.ErrorMessage.INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE;

import com.scalar.db.dataloader.core.ColumnInfo;
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
import com.scalar.db.io.BigIntColumn;
import com.scalar.db.io.BlobColumn;
import com.scalar.db.io.BooleanColumn;
import com.scalar.db.io.Column;
import com.scalar.db.io.DataType;
import com.scalar.db.io.DoubleColumn;
import com.scalar.db.io.FloatColumn;
import com.scalar.db.io.IntColumn;
import com.scalar.db.io.TextColumn;
import java.util.Base64;
import javax.annotation.Nullable;

/**
* Utility class for creating and managing ScalarDB columns.
*
* <p>This class provides methods for creating ScalarDB columns based on the given data type, column
* information, and value. It includes handling for various data types and special cases like base64
* encoding for BLOB data.
*/
public final class ColumnUtils {

/** Restrict instantiation via private constructor */
private ColumnUtils() {}

/**
* Creates a ScalarDB column from the given data type, column information, and value.
*
* <p>Blob source values need to be base64 encoded before passing them as a value. If the value is
* {@code null}, the corresponding column is created as a {@code null} column.
*
* @param dataType the data type of the specified column
* @param columnInfo the ScalarDB table column information
* @param value the value for the ScalarDB column (may be {@code null})
* @return the ScalarDB column created from the specified data
* @throws ColumnParsingException if an error occurs while creating the column or parsing the
* value
*/
public static Column<?> createColumnFromValue(
DataType dataType, ColumnInfo columnInfo, @Nullable String value)
throws ColumnParsingException {
String columnName = columnInfo.getColumnName();
try {
switch (dataType) {
case BOOLEAN:
return value != null
? BooleanColumn.of(columnName, Boolean.parseBoolean(value))
: BooleanColumn.ofNull(columnName);
case INT:
return value != null
? IntColumn.of(columnName, Integer.parseInt(value))
: IntColumn.ofNull(columnName);
case BIGINT:
return value != null
? BigIntColumn.of(columnName, Long.parseLong(value))
: BigIntColumn.ofNull(columnName);
case FLOAT:
return value != null
? FloatColumn.of(columnName, Float.parseFloat(value))
: FloatColumn.ofNull(columnName);
case DOUBLE:
return value != null
? DoubleColumn.of(columnName, Double.parseDouble(value))
: DoubleColumn.ofNull(columnName);
case TEXT:
return value != null ? TextColumn.of(columnName, value) : TextColumn.ofNull(columnName);
case BLOB:
// Source blob values need to be base64 encoded
return value != null
? BlobColumn.of(columnName, Base64.getDecoder().decode(value))
: BlobColumn.ofNull(columnName);
default:
throw new AssertionError();
}
} catch (NumberFormatException e) {
throw new ColumnParsingException(
String.format(
INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE,
columnName,
columnInfo.getTableName(),
columnInfo.getNamespace()),
e);
} catch (IllegalArgumentException e) {
throw new ColumnParsingException(
String.format(
INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE,
columnName,
columnInfo.getTableName(),
columnInfo.getNamespace()),
e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.scalar.db.dataloader.core.util;

import static com.scalar.db.dataloader.core.ErrorMessage.INVALID_COLUMN_NON_EXISTENT;

import com.scalar.db.api.TableMetadata;
import com.scalar.db.dataloader.core.ColumnInfo;
import com.scalar.db.dataloader.core.ColumnKeyValue;
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
import com.scalar.db.dataloader.core.exception.KeyParsingException;
import com.scalar.db.io.Column;
import com.scalar.db.io.DataType;
import com.scalar.db.io.Key;
import javax.annotation.Nullable;

/**
* Utility class for creating and managing ScalarDB keys.
*
* <p>This class provides methods to parse key-value pairs and create ScalarDB key instances. It
* also includes utility methods for handling data types, columns, and potential parsing exceptions.
*/
public final class KeyUtils {

/** Restrict instantiation via private constructor */
private KeyUtils() {}

/**
* Converts a key-value pair, in the format of <key>=<value>, into a ScalarDB Key instance for a
* specific ScalarDB table.
*
* <p>This method uses the provided table metadata to determine the data type for the key and
* creates a corresponding ScalarDB Key. If the key does not match any column in the table
* metadata, a {@link KeyParsingException} is thrown.
*
* @param columnKeyValue a key-value pair in the format of <key>=<value>
* @param namespace the name of the ScalarDB namespace
* @param tableName the name of the ScalarDB table
* @param tableMetadata metadata for the ScalarDB table
* @return a new ScalarDB Key instance formatted according to the data type
* @throws KeyParsingException if there is an error parsing the key value or if the column does
* not exist
*/
@Nullable
public static Key parseKeyValue(
@Nullable ColumnKeyValue columnKeyValue,
String namespace,
String tableName,
TableMetadata tableMetadata)
throws KeyParsingException {
if (columnKeyValue == null) {
return null;
}
String columnName = columnKeyValue.getColumnName();
DataType columnDataType = tableMetadata.getColumnDataType(columnName);
if (columnDataType == null) {
throw new KeyParsingException(
String.format(INVALID_COLUMN_NON_EXISTENT, columnName, tableName, namespace));
}
ColumnInfo columnInfo =
ColumnInfo.builder()
.namespace(namespace)
.tableName(tableName)
.columnName(columnName)
.build();
return createKey(columnDataType, columnInfo, columnKeyValue.getColumnValue());
}

/**
* Creates a ScalarDB key based on the provided data type, column information, and value.
*
* <p>This method creates a ScalarDB Key instance by converting the column value to the
* appropriate data type and constructing the key using that value.
*
* @param dataType the data type of the specified column
* @param columnInfo the ScalarDB table column information
* @param value the value for the ScalarDB key
* @return a ScalarDB Key instance
* @throws KeyParsingException if there is an error while creating the ScalarDB key
*/
public static Key createKey(DataType dataType, ColumnInfo columnInfo, String value)
throws KeyParsingException {
try {
Column<?> keyValue = ColumnUtils.createColumnFromValue(dataType, columnInfo, value);
return Key.newBuilder().add(keyValue).build();
} catch (ColumnParsingException e) {
throw new KeyParsingException(e.getMessage(), e);
}
}
}
Loading

0 comments on commit 6dbe9d4

Please sign in to comment.