You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by mc...@apache.org on 2026/10/09 13:15:22 UTC
(hop) branch main updated: fix(metainject): decode lazy binary-string values when injecting list items, fixes #8812 (#8813)
This is an automated email from the ASF dual-hosted git repository.
mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new ffd3fd3736 fix(metainject): decode lazy binary-string values when injecting list items, fixes #8812 (#8813)
ffd3fd3736 is described below
commit ffd3fd3736ef1e4f76b5d36aa00ffa5795e23967
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Fri Oct 9 15:15:16 2026 +0200
fix(metainject): decode lazy binary-string values when injecting list items, fixes #8812 (#8813)
The HopMetadataInjector rewrite (4c51db7edc) copies values from injection
row buffers into String fields and List<String> fields as-is. When the
source is a lazy-conversion transform (e.g. CSV Input with lazy
conversion enabled), those values are raw byte[] and end up injected as
their toString() representation ("[B@..."), which later breaks
SimpleDateFormat with "Illegal pattern character 'B'" when the value is
used as a format mask (e.g. Calculator conversion_mask).
Decode values with STORAGE_TYPE_BINARY_STRING to their native type via
convertBinaryStringToNativeType() in both injectList() branches, matching
the legacy BeanInjector path which already decoded through
RowMetaAndData.getString()/getAsJavaType().
Adds regression tests for List<String> injection and String fields of
list items fed from lazy byte[] row buffers.
---
.../hop/metadata/inject/HopMetadataInjector.java | 29 +++++++++-
.../metadata/inject/HopMetadataInjectorTest.java | 66 ++++++++++++++++++++++
2 files changed, 93 insertions(+), 2 deletions(-)
diff --git a/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java b/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
index 88ed856739..17b905209a 100644
--- a/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
+++ b/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
@@ -614,8 +614,16 @@ public class HopMetadataInjector {
// Is this a primitive type we're dealing with?
//
if (listItemClass.isPrimitive() || listItemClass.equals(String.class)) {
- // The row contains a single value that we simply need to add to the list
- list.add(row[0]);
+ // The row contains a single value that we simply need to add to the list.
+ // Lazy-converted sources (e.g. CSV Input with lazy conversion) store Strings as
+ // byte[]; decode them so a List<String> receives a String, not "[B@...".
+ //
+ IValueMeta singleValueMeta = rowMeta.getValueMeta(0);
+ Object value = row[0];
+ if (isLazyBinaryString(singleValueMeta, value)) {
+ value = singleValueMeta.convertBinaryStringToNativeType((byte[]) value);
+ }
+ list.add(value);
} else {
// Reuse the pre-defined template item at the same position so its non-injected fields
// are preserved; only create a new item when injecting beyond the pre-defined rows.
@@ -634,6 +642,13 @@ public class HopMetadataInjector {
String valueKey = valueMeta.getName();
Object value = row[valueIndex];
+ // Lazy-converted sources (e.g. CSV Input with lazy conversion) store Strings as
+ // byte[]; decode them so String fields receive a String, not "[B@...".
+ //
+ if (isLazyBinaryString(valueMeta, value)) {
+ value = valueMeta.convertBinaryStringToNativeType((byte[]) value);
+ }
+
// The map will contain just a single value matching the expected key in the underlying
// object
// class field annotation. Only that field will be set.
@@ -659,6 +674,16 @@ public class HopMetadataInjector {
}
}
+ /**
+ * True when the value is a raw lazy-conversion byte array (binary-string storage) that should be
+ * decoded to its native type before it is injected into a String field or list.
+ */
+ private static boolean isLazyBinaryString(IValueMeta valueMeta, Object value) {
+ return valueMeta != null
+ && valueMeta.getStorageType() == IValueMeta.STORAGE_TYPE_BINARY_STRING
+ && value instanceof byte[];
+ }
+
private static void setValue(Object object, Field field, Object valueToSet) throws Exception {
try {
if (valueToSet == null) {
diff --git a/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java b/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
index 194c4e2a83..95dda9ebd0 100644
--- a/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
+++ b/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
@@ -24,6 +24,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,6 +37,7 @@ import org.apache.hop.core.database.NoneDatabaseMeta;
import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowBuffer;
+import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.row.RowMetaBuilder;
import org.apache.hop.core.row.value.ValueMetaDate;
import org.apache.hop.core.row.value.ValueMetaInteger;
@@ -387,4 +390,67 @@ class HopMetadataInjectorTest {
return description;
}
}
+
+ /**
+ * Regression test: a lazy-conversion source (e.g. CSV Input with lazy conversion enabled) stores
+ * values as raw {@code byte[]} with {@link IValueMeta#STORAGE_TYPE_BINARY_STRING}. Injecting such
+ * a value into a {@code List<String>} must decode the bytes to a String, not store the raw array
+ * (which {@code toString()} renders as {@code "[B@..."}).
+ *
+ * <p>The {@code STRING} key on {@link Company} is not a list, so this test verifies the
+ * scalar-path + list-item path via {@code EMPLOYEES/FIRST_NAME} below; this one uses a dedicated
+ * {@code List<String>} sample.
+ */
+ @Test
+ void injectLazyBinaryValueIntoStringList() throws Exception {
+ IValueMeta stringMeta = new ValueMetaString("STRINGS_ITEM");
+ stringMeta.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
+ stringMeta.setStorageMetadata(new ValueMetaString("STRINGS_ITEM"));
+
+ RowBuffer rowBuffer = new RowBuffer();
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(stringMeta);
+ rowBuffer.setRowMeta(rowMeta);
+ rowBuffer.addRow("lazy-value".getBytes(StandardCharsets.UTF_8));
+
+ StringListMeta meta = new StringListMeta();
+ HopMetadataInjector.inject(
+ new MemoryMetadataProvider(), meta, Map.of(), Map.of("STRINGS", rowBuffer));
+
+ assertEquals("lazy-value", meta.getStrings().get(0));
+ }
+
+ /**
+ * Regression test: same lazy-conversion scenario, but the value lands in a String field of a list
+ * item. It must be decoded to a String instead of becoming {@code byte[].toString()} ({@code
+ * "[B@..."}), which later breaks {@code SimpleDateFormat} with "Illegal pattern character 'B'".
+ */
+ @Test
+ void injectLazyBinaryValueIntoListItemStringField() throws Exception {
+ IValueMeta nameMeta = new ValueMetaString("FIRST_NAME");
+ nameMeta.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
+ nameMeta.setStorageMetadata(new ValueMetaString("FIRST_NAME"));
+
+ RowBuffer rowBuffer = new RowBuffer();
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(nameMeta);
+ rowBuffer.setRowMeta(rowMeta);
+ rowBuffer.addRow("Donald".getBytes(StandardCharsets.UTF_8));
+
+ Company company = new Company();
+ HopMetadataInjector.inject(
+ new MemoryMetadataProvider(), company, Map.of(), Map.of("EMPLOYEES", rowBuffer));
+
+ assertEquals("Donald", company.getEmployees().get(0).getFirstName());
+ }
+
+ /** Minimal sample with a {@code List<String>} injection group. */
+ private static class StringListMeta {
+ @HopMetadataProperty(injectionGroupKey = "STRINGS")
+ private final List<String> strings = new ArrayList<>();
+
+ public List<String> getStrings() {
+ return strings;
+ }
+ }
}