Skip to content

Commit 6dbe9d4

Browse files
Manual Backport to branch(3.11) : Add data loader core key and column utils (#2394)
Co-authored-by: Peckstadt <[email protected]>
1 parent b12f821 commit 6dbe9d4

File tree

9 files changed

+537
-1
lines changed

9 files changed

+537
-1
lines changed

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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.scalar.db.dataloader.core;
2+
3+
public class ErrorMessage {
4+
public static final String INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE =
5+
"Invalid number specified for column %s in table %s in namespace %s";
6+
public static final String INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE =
7+
"Invalid base64 encoding for blob value for column %s in table %s in namespace %s";
8+
public static final String INVALID_COLUMN_NON_EXISTENT =
9+
"Invalid key: Column %s does not exist in the table %s in namespace %s.";
10+
}
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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.scalar.db.dataloader.core.util;
2+
3+
import static com.scalar.db.dataloader.core.ErrorMessage.INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE;
4+
import static com.scalar.db.dataloader.core.ErrorMessage.INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE;
5+
6+
import com.scalar.db.dataloader.core.ColumnInfo;
7+
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
8+
import com.scalar.db.io.BigIntColumn;
9+
import com.scalar.db.io.BlobColumn;
10+
import com.scalar.db.io.BooleanColumn;
11+
import com.scalar.db.io.Column;
12+
import com.scalar.db.io.DataType;
13+
import com.scalar.db.io.DoubleColumn;
14+
import com.scalar.db.io.FloatColumn;
15+
import com.scalar.db.io.IntColumn;
16+
import com.scalar.db.io.TextColumn;
17+
import java.util.Base64;
18+
import javax.annotation.Nullable;
19+
20+
/**
21+
* Utility class for creating and managing ScalarDB columns.
22+
*
23+
* <p>This class provides methods for creating ScalarDB columns based on the given data type, column
24+
* information, and value. It includes handling for various data types and special cases like base64
25+
* encoding for BLOB data.
26+
*/
27+
public final class ColumnUtils {
28+
29+
/** Restrict instantiation via private constructor */
30+
private ColumnUtils() {}
31+
32+
/**
33+
* Creates a ScalarDB column from the given data type, column information, and value.
34+
*
35+
* <p>Blob source values need to be base64 encoded before passing them as a value. If the value is
36+
* {@code null}, the corresponding column is created as a {@code null} column.
37+
*
38+
* @param dataType the data type of the specified column
39+
* @param columnInfo the ScalarDB table column information
40+
* @param value the value for the ScalarDB column (may be {@code null})
41+
* @return the ScalarDB column created from the specified data
42+
* @throws ColumnParsingException if an error occurs while creating the column or parsing the
43+
* value
44+
*/
45+
public static Column<?> createColumnFromValue(
46+
DataType dataType, ColumnInfo columnInfo, @Nullable String value)
47+
throws ColumnParsingException {
48+
String columnName = columnInfo.getColumnName();
49+
try {
50+
switch (dataType) {
51+
case BOOLEAN:
52+
return value != null
53+
? BooleanColumn.of(columnName, Boolean.parseBoolean(value))
54+
: BooleanColumn.ofNull(columnName);
55+
case INT:
56+
return value != null
57+
? IntColumn.of(columnName, Integer.parseInt(value))
58+
: IntColumn.ofNull(columnName);
59+
case BIGINT:
60+
return value != null
61+
? BigIntColumn.of(columnName, Long.parseLong(value))
62+
: BigIntColumn.ofNull(columnName);
63+
case FLOAT:
64+
return value != null
65+
? FloatColumn.of(columnName, Float.parseFloat(value))
66+
: FloatColumn.ofNull(columnName);
67+
case DOUBLE:
68+
return value != null
69+
? DoubleColumn.of(columnName, Double.parseDouble(value))
70+
: DoubleColumn.ofNull(columnName);
71+
case TEXT:
72+
return value != null ? TextColumn.of(columnName, value) : TextColumn.ofNull(columnName);
73+
case BLOB:
74+
// Source blob values need to be base64 encoded
75+
return value != null
76+
? BlobColumn.of(columnName, Base64.getDecoder().decode(value))
77+
: BlobColumn.ofNull(columnName);
78+
default:
79+
throw new AssertionError();
80+
}
81+
} catch (NumberFormatException e) {
82+
throw new ColumnParsingException(
83+
String.format(
84+
INVALID_NUMBER_FORMAT_FOR_COLUMN_VALUE,
85+
columnName,
86+
columnInfo.getTableName(),
87+
columnInfo.getNamespace()),
88+
e);
89+
} catch (IllegalArgumentException e) {
90+
throw new ColumnParsingException(
91+
String.format(
92+
INVALID_BASE64_ENCODING_FOR_COLUMN_VALUE,
93+
columnName,
94+
columnInfo.getTableName(),
95+
columnInfo.getNamespace()),
96+
e);
97+
}
98+
}
99+
}
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 static com.scalar.db.dataloader.core.ErrorMessage.INVALID_COLUMN_NON_EXISTENT;
4+
5+
import com.scalar.db.api.TableMetadata;
6+
import com.scalar.db.dataloader.core.ColumnInfo;
7+
import com.scalar.db.dataloader.core.ColumnKeyValue;
8+
import com.scalar.db.dataloader.core.exception.ColumnParsingException;
9+
import com.scalar.db.dataloader.core.exception.KeyParsingException;
10+
import com.scalar.db.io.Column;
11+
import com.scalar.db.io.DataType;
12+
import com.scalar.db.io.Key;
13+
import javax.annotation.Nullable;
14+
15+
/**
16+
* Utility class for creating and managing ScalarDB keys.
17+
*
18+
* <p>This class provides methods to parse key-value pairs and create ScalarDB key instances. It
19+
* also includes utility methods for handling data types, columns, and potential parsing exceptions.
20+
*/
21+
public final class KeyUtils {
22+
23+
/** Restrict instantiation via private constructor */
24+
private KeyUtils() {}
25+
26+
/**
27+
* Converts a key-value pair, in the format of <key>=<value>, into a ScalarDB Key instance for a
28+
* specific ScalarDB table.
29+
*
30+
* <p>This method uses the provided table metadata to determine the data type for the key and
31+
* creates a corresponding ScalarDB Key. If the key does not match any column in the table
32+
* metadata, a {@link KeyParsingException} is thrown.
33+
*
34+
* @param columnKeyValue a key-value pair in the format of <key>=<value>
35+
* @param namespace the name of the ScalarDB namespace
36+
* @param tableName the name of the ScalarDB table
37+
* @param tableMetadata metadata for the ScalarDB table
38+
* @return a new ScalarDB Key instance formatted according to the data type
39+
* @throws KeyParsingException if there is an error parsing the key value or if the column does
40+
* not exist
41+
*/
42+
@Nullable
43+
public static Key parseKeyValue(
44+
@Nullable ColumnKeyValue columnKeyValue,
45+
String namespace,
46+
String tableName,
47+
TableMetadata tableMetadata)
48+
throws KeyParsingException {
49+
if (columnKeyValue == null) {
50+
return null;
51+
}
52+
String columnName = columnKeyValue.getColumnName();
53+
DataType columnDataType = tableMetadata.getColumnDataType(columnName);
54+
if (columnDataType == null) {
55+
throw new KeyParsingException(
56+
String.format(INVALID_COLUMN_NON_EXISTENT, 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)