Skip to content

Commit f876df7

Browse files
Manual Backport to branch(3.12) : Add data loader core key and column utils (#2396)
Co-authored-by: Peckstadt <[email protected]>
1 parent e633d3b commit f876df7

File tree

9 files changed

+540
-1
lines changed

9 files changed

+540
-1
lines changed

core/src/main/java/com/scalar/db/common/error/CoreError.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,25 @@ public enum CoreError implements ScalarDbError {
606606
"",
607607
""),
608608

609+
DATA_LOADER_INVALID_COLUMN_NON_EXISTENT(
610+
Category.USER_ERROR,
611+
"0148",
612+
"Invalid key: Column %s does not exist in the table %s in namespace %s.",
613+
"",
614+
""),
615+
DATA_LOADER_INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE(
616+
Category.USER_ERROR,
617+
"0149",
618+
"Invalid base64 encoding for blob value for column %s in table %s in namespace %s",
619+
"",
620+
""),
621+
DATA_LOADER_INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE(
622+
Category.USER_ERROR,
623+
"0150",
624+
"Invalid number specified for column %s in table %s in namespace %s",
625+
"",
626+
""),
627+
609628
//
610629
// Errors for the concurrency error category
611630
//

data-loader/core/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ archivesBaseName = "scalardb-data-loader-core"
99
dependencies {
1010
// ScalarDB core
1111
implementation project(':core')
12-
12+
1313
// for SpotBugs
1414
compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}"
1515
testCompileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.scalar.db.dataloader.core;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
/**
7+
* Represents a column in a database table.
8+
*
9+
* <p>This class holds the metadata for a column, including the namespace (schema), table name, and
10+
* the column name within the table.
11+
*/
12+
@Value
13+
@Builder
14+
public class ColumnInfo {
15+
16+
/** The namespace (schema) where the table is located. */
17+
String namespace;
18+
19+
/** The name of the table where the column resides. */
20+
String tableName;
21+
22+
/** The name of the column in the table. */
23+
String columnName;
24+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.scalar.db.dataloader.core.exception;
2+
3+
/**
4+
* An exception that is thrown when an error occurs while trying to create a ScalarDB column from a
5+
* value.
6+
*
7+
* <p>This exception is typically used to indicate a problem with parsing or converting data into a
8+
* format that can be used to create a column in ScalarDB.
9+
*/
10+
public class ColumnParsingException extends Exception {
11+
12+
/**
13+
* Constructs a new {@code ColumnParsingException} with the specified detail message.
14+
*
15+
* @param message the detail message explaining the cause of the exception
16+
*/
17+
public ColumnParsingException(String message) {
18+
super(message);
19+
}
20+
21+
/**
22+
* Constructs a new {@code ColumnParsingException} with the specified detail message and cause.
23+
*
24+
* @param message the detail message explaining the cause of the exception
25+
* @param cause the cause of the exception (can be {@code null})
26+
*/
27+
public ColumnParsingException(String message, Throwable cause) {
28+
super(message, cause);
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.scalar.db.dataloader.core.exception;
2+
3+
/**
4+
* An exception that is thrown when an error occurs while trying to create a ScalarDB key from a
5+
* value.
6+
*
7+
* <p>This exception is typically used to indicate a problem with parsing or converting data into a
8+
* format that can be used to create a key in ScalarDB.
9+
*/
10+
public class KeyParsingException extends Exception {
11+
12+
/**
13+
* Constructs a new {@code KeyParsingException} with the specified detail message.
14+
*
15+
* @param message the detail message explaining the cause of the exception
16+
*/
17+
public KeyParsingException(String message) {
18+
super(message);
19+
}
20+
21+
/**
22+
* Constructs a new {@code KeyParsingException} with the specified detail message and cause.
23+
*
24+
* @param message the detail message explaining the cause of the exception
25+
* @param cause the cause of the exception (can be {@code null})
26+
*/
27+
public KeyParsingException(String message, Throwable cause) {
28+
super(message, cause);
29+
}
30+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.scalar.db.dataloader.core.util;
2+
3+
import com.scalar.db.common.error.CoreError;
4+
import com.scalar.db.dataloader.core.ColumnInfo;
5+
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
6+
import com.scalar.db.io.BigIntColumn;
7+
import com.scalar.db.io.BlobColumn;
8+
import com.scalar.db.io.BooleanColumn;
9+
import com.scalar.db.io.Column;
10+
import com.scalar.db.io.DataType;
11+
import com.scalar.db.io.DoubleColumn;
12+
import com.scalar.db.io.FloatColumn;
13+
import com.scalar.db.io.IntColumn;
14+
import com.scalar.db.io.TextColumn;
15+
import java.util.Base64;
16+
import javax.annotation.Nullable;
17+
18+
/**
19+
* Utility class for creating and managing ScalarDB columns.
20+
*
21+
* <p>This class provides methods for creating ScalarDB columns based on the given data type, column
22+
* information, and value. It includes handling for various data types and special cases like base64
23+
* encoding for BLOB data.
24+
*/
25+
public final class ColumnUtils {
26+
27+
/** Restrict instantiation via private constructor */
28+
private ColumnUtils() {}
29+
30+
/**
31+
* Creates a ScalarDB column from the given data type, column information, and value.
32+
*
33+
* <p>Blob source values need to be base64 encoded before passing them as a value. If the value is
34+
* {@code null}, the corresponding column is created as a {@code null} column.
35+
*
36+
* @param dataType the data type of the specified column
37+
* @param columnInfo the ScalarDB table column information
38+
* @param value the value for the ScalarDB column (may be {@code null})
39+
* @return the ScalarDB column created from the specified data
40+
* @throws ColumnParsingException if an error occurs while creating the column or parsing the
41+
* value
42+
*/
43+
public static Column<?> createColumnFromValue(
44+
DataType dataType, ColumnInfo columnInfo, @Nullable String value)
45+
throws ColumnParsingException {
46+
String columnName = columnInfo.getColumnName();
47+
try {
48+
switch (dataType) {
49+
case BOOLEAN:
50+
return value != null
51+
? BooleanColumn.of(columnName, Boolean.parseBoolean(value))
52+
: BooleanColumn.ofNull(columnName);
53+
case INT:
54+
return value != null
55+
? IntColumn.of(columnName, Integer.parseInt(value))
56+
: IntColumn.ofNull(columnName);
57+
case BIGINT:
58+
return value != null
59+
? BigIntColumn.of(columnName, Long.parseLong(value))
60+
: BigIntColumn.ofNull(columnName);
61+
case FLOAT:
62+
return value != null
63+
? FloatColumn.of(columnName, Float.parseFloat(value))
64+
: FloatColumn.ofNull(columnName);
65+
case DOUBLE:
66+
return value != null
67+
? DoubleColumn.of(columnName, Double.parseDouble(value))
68+
: DoubleColumn.ofNull(columnName);
69+
case TEXT:
70+
return value != null ? TextColumn.of(columnName, value) : TextColumn.ofNull(columnName);
71+
case BLOB:
72+
// Source blob values need to be base64 encoded
73+
return value != null
74+
? BlobColumn.of(columnName, Base64.getDecoder().decode(value))
75+
: BlobColumn.ofNull(columnName);
76+
default:
77+
throw new AssertionError();
78+
}
79+
} catch (NumberFormatException e) {
80+
throw new ColumnParsingException(
81+
CoreError.DATA_LOADER_INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE.buildMessage(
82+
columnName, columnInfo.getTableName(), columnInfo.getNamespace()),
83+
e);
84+
} catch (IllegalArgumentException e) {
85+
throw new ColumnParsingException(
86+
CoreError.DATA_LOADER_INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE.buildMessage(
87+
columnName, columnInfo.getTableName(), columnInfo.getNamespace()),
88+
e);
89+
}
90+
}
91+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.scalar.db.dataloader.core.util;
2+
3+
import com.scalar.db.api.TableMetadata;
4+
import com.scalar.db.common.error.CoreError;
5+
import com.scalar.db.dataloader.core.ColumnInfo;
6+
import com.scalar.db.dataloader.core.ColumnKeyValue;
7+
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
8+
import com.scalar.db.dataloader.core.exception.KeyParsingException;
9+
import com.scalar.db.io.Column;
10+
import com.scalar.db.io.DataType;
11+
import com.scalar.db.io.Key;
12+
import javax.annotation.Nullable;
13+
14+
/**
15+
* Utility class for creating and managing ScalarDB keys.
16+
*
17+
* <p>This class provides methods to parse key-value pairs and create ScalarDB key instances. It
18+
* also includes utility methods for handling data types, columns, and potential parsing exceptions.
19+
*/
20+
public final class KeyUtils {
21+
22+
/** Restrict instantiation via private constructor */
23+
private KeyUtils() {}
24+
25+
/**
26+
* Converts a key-value pair, in the format of <key>=<value>, into a ScalarDB Key instance for a
27+
* specific ScalarDB table.
28+
*
29+
* <p>This method uses the provided table metadata to determine the data type for the key and
30+
* creates a corresponding ScalarDB Key. If the key does not match any column in the table
31+
* metadata, a {@link KeyParsingException} is thrown.
32+
*
33+
* @param columnKeyValue a key-value pair in the format of <key>=<value>
34+
* @param namespace the name of the ScalarDB namespace
35+
* @param tableName the name of the ScalarDB table
36+
* @param tableMetadata metadata for the ScalarDB table
37+
* @return a new ScalarDB Key instance formatted according to the data type
38+
* @throws KeyParsingException if there is an error parsing the key value or if the column does
39+
* not exist
40+
*/
41+
@Nullable
42+
public static Key parseKeyValue(
43+
@Nullable ColumnKeyValue columnKeyValue,
44+
String namespace,
45+
String tableName,
46+
TableMetadata tableMetadata)
47+
throws KeyParsingException {
48+
if (columnKeyValue == null) {
49+
return null;
50+
}
51+
String columnName = columnKeyValue.getColumnName();
52+
DataType columnDataType = tableMetadata.getColumnDataType(columnName);
53+
if (columnDataType == null) {
54+
throw new KeyParsingException(
55+
CoreError.DATA_LOADER_INVALID_COLUMN_NON_EXISTENT.buildMessage(
56+
columnName, tableName, namespace));
57+
}
58+
ColumnInfo columnInfo =
59+
ColumnInfo.builder()
60+
.namespace(namespace)
61+
.tableName(tableName)
62+
.columnName(columnName)
63+
.build();
64+
return createKey(columnDataType, columnInfo, columnKeyValue.getColumnValue());
65+
}
66+
67+
/**
68+
* Creates a ScalarDB key based on the provided data type, column information, and value.
69+
*
70+
* <p>This method creates a ScalarDB Key instance by converting the column value to the
71+
* appropriate data type and constructing the key using that value.
72+
*
73+
* @param dataType the data type of the specified column
74+
* @param columnInfo the ScalarDB table column information
75+
* @param value the value for the ScalarDB key
76+
* @return a ScalarDB Key instance
77+
* @throws KeyParsingException if there is an error while creating the ScalarDB key
78+
*/
79+
public static Key createKey(DataType dataType, ColumnInfo columnInfo, String value)
80+
throws KeyParsingException {
81+
try {
82+
Column<?> keyValue = ColumnUtils.createColumnFromValue(dataType, columnInfo, value);
83+
return Key.newBuilder().add(keyValue).build();
84+
} catch (ColumnParsingException e) {
85+
throw new KeyParsingException(e.getMessage(), e);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)