Skip to content

feat(QTDI-1305): improve error in record #1041

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

Merged
merged 68 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
c924108
Add withError in Record.Builder
yyin-talend Apr 21, 2025
ae4cad7
Test for AvroRecord
yyin-talend Apr 21, 2025
9205765
fix isValid
yyin-talend Apr 22, 2025
cfa68e8
Merge branch 'master' into yyin/QTDI-1305-ImproveErrorInRecord
yyin-talend Apr 22, 2025
cf0a3c6
Add sample-feature\supporterror
yyin-talend Apr 30, 2025
7d049d1
worked
yyin-talend Apr 30, 2025
441faae
fix bug
yyin-talend May 6, 2025
0d8f532
Merge branch 'master' into yyin/QTDI-1305-ImproveErrorInRecord
yyin-talend May 6, 2025
de71297
Add new method to white list.
yyin-talend May 6, 2025
d5ab539
Add "valid":true to serialization
yyin-talend May 6, 2025
86ad7ee
Add "valid":true to serialization
yyin-talend May 6, 2025
884420f
Add "valid":true to serialization
yyin-talend May 7, 2025
a9450e3
rename
yyin-talend May 7, 2025
727c512
revert junit which no need to change
yyin-talend May 7, 2025
e2dbb27
fix one problem
yyin-talend May 7, 2025
21cccfc
Merge branch 'master' into yyin/QTDI-1305-ImproveErrorInRecord
yyin-talend May 7, 2025
d2cf309
fix import order
yyin-talend May 7, 2025
02a5ebd
fix import order
yyin-talend May 7, 2025
92df0c0
change command jar
yyin-talend May 8, 2025
fb6a891
fix sonar
yyin-talend May 8, 2025
7314bfb
remove comment
yyin-talend May 8, 2025
01389f5
add readme
yyin-talend May 9, 2025
7884086
remove withError in record API
yyin-talend May 12, 2025
e9ea736
add getErrorMessage,getErrorFallbackValue in Entry API
yyin-talend May 12, 2025
8633ea1
feat(QTDI-1305): Fix RecordBuilder builder pattern +some improvements…
ypiel-talend May 13, 2025
9e8120a
Merge branch 'master' into yyin/QTDI-1305-ImproveErrorInRecord-2
yyin-talend May 13, 2025
6e3ad2b
feat(QTDI-1305): set RecordImpl#BuilderImpl#entriesInError final.
ypiel-talend May 13, 2025
9df828e
feat(QTDI-1305): Fix entry with error in AvroRecord + unit test.
ypiel-talend May 13, 2025
fc71e1a
add transfer "errorcapable" between Avro.Field and Schema.Entry.
yyin-talend May 14, 2025
b4e2ad3
feat(QTDI-1305): Fix some unit test + improve sample CLI
ypiel-talend May 15, 2025
c5e38bb
Add withError in Record.Builder
yyin-talend Apr 21, 2025
5ba8a4b
Test for AvroRecord
yyin-talend Apr 21, 2025
2ce8bc8
fix isValid
yyin-talend Apr 22, 2025
beca024
Add sample-feature\supporterror
yyin-talend Apr 30, 2025
ad5aeae
worked
yyin-talend Apr 30, 2025
53d8c05
fix bug
yyin-talend May 6, 2025
327161f
Add new method to white list.
yyin-talend May 6, 2025
92b02ae
Add "valid":true to serialization
yyin-talend May 6, 2025
042b9aa
Add "valid":true to serialization
yyin-talend May 6, 2025
a7b28c2
Add "valid":true to serialization
yyin-talend May 7, 2025
68e1f0d
rename
yyin-talend May 7, 2025
c51e830
revert junit which no need to change
yyin-talend May 7, 2025
a830a96
fix one problem
yyin-talend May 7, 2025
1d46e1f
fix import order
yyin-talend May 7, 2025
a537dfd
fix import order
yyin-talend May 7, 2025
fb73c8d
change command jar
yyin-talend May 8, 2025
9a408f4
fix sonar
yyin-talend May 8, 2025
5af6128
remove comment
yyin-talend May 8, 2025
a3b0fda
add readme
yyin-talend May 9, 2025
96b5897
remove withError in record API
yyin-talend May 12, 2025
c87b923
add getErrorMessage,getErrorFallbackValue in Entry API
yyin-talend May 12, 2025
05b7b3f
feat(QTDI-1305): Fix RecordBuilder builder pattern +some improvements…
ypiel-talend May 13, 2025
684f1ee
feat(QTDI-1305): set RecordImpl#BuilderImpl#entriesInError final.
ypiel-talend May 13, 2025
a39f2be
feat(QTDI-1305): Fix entry with error in AvroRecord + unit test.
ypiel-talend May 13, 2025
41fb00b
add transfer "errorcapable" between Avro.Field and Schema.Entry.
yyin-talend May 14, 2025
8ca7681
feat(QTDI-1305): Fix some unit test + improve sample CLI
ypiel-talend May 15, 2025
d563561
Merge branch 'yyin/QTDI-1305-ImproveErrorInRecord-2' of github.com:Ta…
ypiel-talend May 15, 2025
493d65a
feat(QTDI-1305): the CLI display error in 'name' attribute.
ypiel-talend May 15, 2025
d9efb30
Merge branch 'yyin/QTDI-1305-ImproveErrorInRecord-2' of github.com:Ta…
ypiel-talend May 15, 2025
a31416e
feat(QTDI-1305): the CLI display value even when error.
ypiel-talend May 15, 2025
2e24616
feat(QTDI-1305): Fix icons.
ypiel-talend May 15, 2025
d0b2bf9
feat(QTDI-1305): Improve properties keys.
ypiel-talend May 15, 2025
7b84705
feat(QTDI-1305): Clean code.
ypiel-talend May 15, 2025
c7c3f49
feat(QTDI-1305): Clean code.
ypiel-talend May 15, 2025
aaf7453
feat(QTDI-1305): Fix readme.
ypiel-talend May 15, 2025
a1840c9
feat(QTDI-1305): Adda property on IndexedRecord/schema to check if th…
ypiel-talend May 16, 2025
d5e09df
feat(QTDI-1305): Display entries original name in error.
ypiel-talend May 16, 2025
2780de7
feat(QTDI-1305): Add a unit test.
ypiel-talend May 16, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

public interface Record {

String RECORD_ERROR_SUPPORT = "talend.component.record.error.support";

/**
* @return the schema of this record.
*/
Expand Down Expand Up @@ -311,6 +313,11 @@ default Optional<Record> getOptionalRecord(final String name) {
return ofNullable(get(Record.class, name));
}

default boolean isValid() {
return !getSchema().getAllEntries()
.anyMatch(entry -> !entry.isValid());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See sonar comment, more easy to read that double negation.

}

/**
* Allows to create a record with a fluent API. This is the unique recommended way to create a record.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,16 @@ interface Entry {
*/
boolean isMetadata();

/**
* @return Is this entry can be in error.
*/
boolean isErrorCapable();

/**
* @return true if the value of this entry is valid; false for invalid value.
*/
boolean isValid();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also add a default behavior as returning true...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not using schema, but using Entry.


/**
* @param <T> the default value type.
*
Expand Down Expand Up @@ -296,6 +306,14 @@ default Entry.Builder toBuilder() {
throw new UnsupportedOperationException("#toBuilder is not implemented");
}

default String getErrorMessage() {
return getProp(SchemaProperty.ENTRY_ERROR_MESSAGE);
}

default String getErrorFallbackValue() {
return getProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE);
}

/**
* Plain builder matching {@link Entry} structure.
*/
Expand All @@ -317,6 +335,8 @@ default Builder withLogicalType(String logicalType) {

Builder withNullable(boolean nullable);

Builder withErrorCapable(boolean errorCapable);

Builder withMetadata(boolean metadata);

<T> Builder withDefaultValue(T value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public interface SchemaProperty {

String ALLOW_SPECIAL_NAME = "field.special.name";

String ENTRY_IS_ON_ERROR = "record.value.on.error";

String ENTRY_ERROR_MESSAGE = "record.value.on.error.message";

String ENTRY_ERROR_FALLBACK_VALUE = "record.value.on.error.fallback_value";

enum LogicalType {

DATE("date"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ public boolean isNullable() {
return true;
}

@Override
public boolean isErrorCapable() {
return false;
}

@Override
public boolean isMetadata() {
return false;
Expand Down Expand Up @@ -163,6 +168,12 @@ public String getProp(final String property) {
public Builder toBuilder() {
throw new UnsupportedOperationException("#toBuilder()");
}

@Override
public boolean isValid() {
return true;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public boolean isNullable() {
return false;
}

@Override
public boolean isErrorCapable() {
return false;
}

@Override
public boolean isMetadata() {
return false;
Expand Down Expand Up @@ -135,6 +140,11 @@ public Map<String, String> getProps() {
public String getProp(String property) {
return null;
}

@Override
public boolean isValid() {
return true;
}
};
Assertions.assertEquals("value", record.get(String.class, e1));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ public boolean isNullable() {
throw new UnsupportedOperationException("#isNullable()");
}

@Override
public boolean isErrorCapable() {
throw new UnsupportedOperationException("#isErrorCapable()");
}

@Override
public boolean isMetadata() {
throw new UnsupportedOperationException("#isMetadata()");
Expand Down Expand Up @@ -326,6 +331,11 @@ public JsonValue getJsonProp(final String name) {
return Entry.super.getJsonProp(name);
}

@Override
public boolean isValid() {
return true;
}

}

class SchemaBuilder implements Schema.Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ static class MockEntry implements Schema.Entry {

private final boolean nullable;

private final boolean errorCapable;

private final boolean metadata;

private final Object defaultVal;
Expand All @@ -102,6 +104,11 @@ public String getProp(final String property) {
public Builder toBuilder() {
throw new UnsupportedOperationException("#toBuilder()");
}

@Override
public boolean isValid() {
return true;
}
}

@RequiredArgsConstructor
Expand Down Expand Up @@ -135,6 +142,12 @@ public Entry.Builder withNullable(boolean nullable) {
return this;
}

@Override
public Entry.Builder withErrorCapable(boolean errorCapable) {
this.builder.withErrorCapable(errorCapable);
return this;
}

@Override
public Entry.Builder withMetadata(boolean metadata) {
this.builder.withMetadata(metadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public AvroRecord(final Record record) {
this.schema = AvroSchema.toAvroSchema(record.getSchema());
this.delegate = new GenericData.Record(this.schema.getActualDelegate());

this.delegate.getSchema().addProp(KeysForAvroProperty.RECORD_IN_ERROR, String.valueOf(!record.isValid()));

record
.getSchema()
.getAllEntries()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,19 +216,23 @@ private Entry fromAvro(final Field field) {
}

private static Entry buildFromAvro(final Field field, final Type type,
final Optional<SchemaProperty.LogicalType> logicalType, final AvroSchema elementSchema) {
final Optional<SchemaProperty.LogicalType> logicalType,
final AvroSchema elementSchema) {
Entry.Builder builder = new EntryImpl.BuilderImpl() //
.withName(field.name()) //
.withRawName(field.getProp(KeysForAvroProperty.LABEL)) //
.withType(type) //
.withNullable(field.schema().getType() == UNION) //
.withErrorCapable(Boolean.parseBoolean(field.getProp(KeysForAvroProperty.IS_ERROR_CAPABLE)))
.withMetadata(AvroSchema.isMetadata(field)) //
.withDefaultValue(field.defaultVal()) //
.withElementSchema(elementSchema) //
.withComment(field.doc()) //
.withProps(field.getObjectProps()
.entrySet()
.stream()
// KeysForAvroProperty.IS_ERROR_CAPABLE is already managed above
.filter(p -> !p.getKey().equals(KeysForAvroProperty.IS_ERROR_CAPABLE))
.collect(toMap(Map.Entry::getKey, e -> String.valueOf(e.getValue()))));

logicalType.ifPresent(builder::withLogicalType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,9 @@ public static Field toField(final org.apache.avro.Schema schema, final Schema.En
for (Map.Entry<String, String> e : entry.getProps().entrySet()) {
field.addProp(e.getKey(), e.getValue());
}

if (entry.isErrorCapable()) {
field.addProp(KeysForAvroProperty.IS_ERROR_CAPABLE, String.valueOf(entry.isErrorCapable()));
}
return field;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ public interface KeysForAvroProperty {

// alias that indicate field is metadata if present.
String METADATA_ALIAS_NAME = "talend.field.__METADATA__";

String IS_ERROR_CAPABLE = "talend.component.record.entry.errorCapable";

String RECORD_IN_ERROR = "talend.component.record.value.on.error";
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Copyright (C) 2006-2025 Talend Inc. - www.talend.com
*
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
*
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -41,6 +41,7 @@
import javax.json.JsonArray;
import javax.json.JsonObject;

import org.apache.avro.Schema.Field;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.IndexedRecord;
import org.apache.avro.io.EncoderFactory;
Expand Down Expand Up @@ -209,6 +210,108 @@
Assertions.assertNotNull(arrayType);
}

@Test
void testWithoutErrorSupport() {
Assertions.assertThrows(IllegalArgumentException.class, () -> testWithError("false", true));
}

@Test
void testWithErrorSupportButNoError() {
Record record = testWithError("true", false);

IndexedRecord unwrap = ((AvroRecord) record).unwrap(IndexedRecord.class);

Assertions.assertTrue(unwrap.getSchema().getProp(KeysForAvroProperty.RECORD_IN_ERROR).equals("false"));

Check warning on line 224 in component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java

View check run for this annotation

sonar-eks / Component Runtime Sonarqube Results

component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java#L224

Use assertEquals instead.

Field nameField = unwrap.getSchema().getFields().get(0);
Assertions.assertNull(nameField.getProp(SchemaProperty.ENTRY_IS_ON_ERROR));

Field ageField = unwrap.getSchema().getFields().get(2);
Assertions.assertNull(ageField.getProp(SchemaProperty.ENTRY_IS_ON_ERROR));
}

@Test
void testWithErrorSupport() {
Record record = testWithError("true", true);
assertFalse(record.isValid());

final Schema.Entry entry = record.getSchema().getEntry("name");
assertNotNull(entry);
Assertions.assertFalse(entry.isValid());
Assertions.assertNull(record.getString("name"));

final Schema.Entry entry2 = record.getSchema().getEntry("age");
assertNotNull(entry2);
Assertions.assertFalse(entry2.isValid());
Assertions.assertNull(record.get(Integer.class, "age"));

IndexedRecord unwrap = ((AvroRecord) record).unwrap(IndexedRecord.class);

Assertions.assertTrue(unwrap.getSchema().getProp(KeysForAvroProperty.RECORD_IN_ERROR).equals("true"));

Check warning on line 250 in component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java

View check run for this annotation

sonar-eks / Component Runtime Sonarqube Results

component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java#L250

Use assertEquals instead.

Field nameField = unwrap.getSchema().getFields().get(0);
Assertions.assertEquals("true",
nameField.getProp(SchemaProperty.ENTRY_IS_ON_ERROR));
Assertions.assertEquals("Entry 'name' is not nullable",
nameField.getProp(SchemaProperty.ENTRY_ERROR_MESSAGE));
Assertions.assertEquals("null",
nameField.getProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE));

Field ageField = unwrap.getSchema().getFields().get(2);
Assertions.assertEquals("true",
ageField.getProp(SchemaProperty.ENTRY_IS_ON_ERROR));
Assertions.assertEquals("Entry 'age' of type INT is not compatible with given value of type " +
"'java.lang.String': 'is not an int'.",
ageField.getProp(SchemaProperty.ENTRY_ERROR_MESSAGE));
Assertions.assertEquals("is not an int",
ageField.getProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE));

Field noErrorField = unwrap.getSchema().getFields().get(1);
Assertions.assertNull(noErrorField.getProp(SchemaProperty.ENTRY_IS_ON_ERROR));
Assertions.assertNull(noErrorField.getProp(SchemaProperty.ENTRY_ERROR_MESSAGE));
Assertions.assertNull(noErrorField.getProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE));

}

private Record testWithError(final String supported, final boolean genError) {
final String errorSupportBackup = System.getProperty(Record.RECORD_ERROR_SUPPORT);
System.setProperty(Record.RECORD_ERROR_SUPPORT, supported);

org.talend.sdk.component.api.record.Schema.Builder schemaBuilder = factory.newSchemaBuilder(Schema.Type.RECORD);
Schema.Entry nameEntry = factory
.newEntryBuilder()
.withName("name")
.withErrorCapable(true)
.withNullable(false)
.withType(Schema.Type.STRING)
.build();
Schema.Entry noErrorEntry = factory
.newEntryBuilder()
.withName("normal")
.withErrorCapable(true)
.withNullable(true)
.withType(Schema.Type.STRING)
.build();
Schema.Entry ageEntry = factory
.newEntryBuilder()
.withName("age")
.withErrorCapable(true)
.withNullable(false)
.withType(Schema.Type.INT)
.build();
Schema customerSchema = schemaBuilder.withEntry(nameEntry).withEntry(noErrorEntry).withEntry(ageEntry).build();

Record.Builder recordBuilder = factory.newRecordBuilder(customerSchema);
Record record = recordBuilder.with(nameEntry, genError ? null : "a string")
.with(noErrorEntry, "normal")
.with(ageEntry, genError ? "is not an int" : 10)
.build();

System.setProperty(Record.RECORD_ERROR_SUPPORT, errorSupportBackup == null ? "false" : errorSupportBackup);

return record;
}

@Test
void recordWithNewSchema() {
final Schema schema0 = new AvroSchemaBuilder()//
Expand Down Expand Up @@ -520,8 +623,8 @@
}

private Schema.Entry newEntry(final String name, String rawname, Schema.Type type, boolean nullable,
Object defaultValue,
String comment) {
Object defaultValue,
String comment) {
return new EntryImpl.BuilderImpl()
.withName(name)
.withRawName(rawname)
Expand All @@ -537,7 +640,7 @@
}

private Schema.Entry newMetaEntry(final String name, String rawname, Schema.Type type, boolean nullable,
Object defaultValue, String comment) {
Object defaultValue, String comment) {
return new EntryImpl.BuilderImpl()
.withName(name)
.withRawName(rawname)
Expand Down
Loading
Loading