Skip to content

Commit 7a76c43

Browse files
authored
[core] CastFieldGetter throw exception with field when cast fail (apache#7515)
### Purpose Purpose: To include field names in exception information when type conversion fails, helping users locate the problem faster. ### Tests `DataTypeCastTableTest`
1 parent 098ef8e commit 7a76c43

File tree

3 files changed

+125
-5
lines changed

3 files changed

+125
-5
lines changed

paimon-common/src/main/java/org/apache/paimon/casting/CastFieldGetter.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,31 @@ public class CastFieldGetter {
2727

2828
private final InternalRow.FieldGetter fieldGetter;
2929
private final CastExecutor<Object, Object> castExecutor;
30+
private final String fieldName;
3031

3132
@SuppressWarnings("unchecked")
32-
public CastFieldGetter(InternalRow.FieldGetter fieldGetter, CastExecutor<?, ?> castExecutor) {
33+
public CastFieldGetter(
34+
InternalRow.FieldGetter fieldGetter,
35+
CastExecutor<?, ?> castExecutor,
36+
String fieldName) {
3337
this.fieldGetter = fieldGetter;
3438
this.castExecutor = (CastExecutor<Object, Object>) castExecutor;
39+
this.fieldName = fieldName;
3540
}
3641

3742
@SuppressWarnings("unchecked")
3843
public <V> V getFieldOrNull(InternalRow row) {
3944
Object value = fieldGetter.getFieldOrNull(row);
40-
return value == null ? null : (V) castExecutor.cast(value);
45+
if (value == null) {
46+
return null;
47+
}
48+
try {
49+
return (V) castExecutor.cast(value);
50+
} catch (Exception e) {
51+
throw new RuntimeException(
52+
String.format(
53+
"Failed to cast value for field '%s': %s", fieldName, e.getMessage()),
54+
e);
55+
}
4156
}
4257
}

paimon-core/src/main/java/org/apache/paimon/schema/SchemaEvolutionUtil.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,15 @@ private static CastFieldGetter[] createCastFieldGetterMapping(
223223
boolean castExist = false;
224224

225225
for (int i = 0; i < tableFields.size(); i++) {
226+
DataField tableField = tableFields.get(i);
226227
int dataIndex = indexMapping == null ? i : indexMapping[i];
227228
if (dataIndex < 0) {
228229
converterMapping[i] =
229-
new CastFieldGetter(row -> null, CastExecutors.identityCastExecutor());
230+
new CastFieldGetter(
231+
row -> null,
232+
CastExecutors.identityCastExecutor(),
233+
tableField.name());
230234
} else {
231-
DataField tableField = tableFields.get(i);
232235
DataField dataField = dataFields.get(dataIndex);
233236
if (!dataField.type().equalsIgnoreNullable(tableField.type())) {
234237
castExist = true;
@@ -238,7 +241,8 @@ private static CastFieldGetter[] createCastFieldGetterMapping(
238241
converterMapping[i] =
239242
new CastFieldGetter(
240243
InternalRowUtils.createNullCheckingFieldGetter(dataField.type(), i),
241-
createCastExecutor(dataField.type(), tableField.type()));
244+
createCastExecutor(dataField.type(), tableField.type()),
245+
tableField.name());
242246
}
243247
}
244248

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.paimon.table;
20+
21+
import org.apache.paimon.data.BinaryString;
22+
import org.apache.paimon.data.GenericRow;
23+
import org.apache.paimon.schema.Schema;
24+
import org.apache.paimon.schema.SchemaChange;
25+
import org.apache.paimon.types.DataTypes;
26+
27+
import org.junit.jupiter.api.Test;
28+
29+
import java.util.Collections;
30+
31+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
32+
33+
/** Test for data type cast error message contains column name. */
34+
public class DataTypeCastTableTest extends TableTestBase {
35+
36+
@Test
37+
public void testStringToIntCastErrorMessageContainsColumnName() throws Exception {
38+
// Create table with STRING column
39+
Schema schema =
40+
Schema.newBuilder()
41+
.column("pk", DataTypes.INT())
42+
.column("str_col", DataTypes.STRING())
43+
.primaryKey("pk")
44+
.option("bucket", "1")
45+
.build();
46+
catalog.createTable(identifier("CastTestTable"), schema, true);
47+
48+
// Write data with non-numeric string that cannot be cast to INT
49+
FileStoreTable table = getTable(identifier("CastTestTable"));
50+
write(
51+
table,
52+
GenericRow.of(1, BinaryString.fromString("not_a_number")),
53+
GenericRow.of(2, BinaryString.fromString("123")),
54+
GenericRow.of(3, BinaryString.fromString("invalid_value")));
55+
56+
// Alter table: change column type from STRING to INT
57+
catalog.alterTable(
58+
identifier("CastTestTable"),
59+
Collections.singletonList(
60+
SchemaChange.updateColumnType("str_col", DataTypes.INT())),
61+
false);
62+
63+
// Read the table, should throw exception with column name in message
64+
FileStoreTable alteredTable = getTable(identifier("CastTestTable"));
65+
assertThatThrownBy(() -> read(alteredTable))
66+
.isInstanceOf(RuntimeException.class)
67+
.hasMessageContaining("Failed to cast value for field 'str_col'");
68+
}
69+
70+
@Test
71+
public void testStringToIntCastSuccess() throws Exception {
72+
// Create table with STRING column
73+
Schema schema =
74+
Schema.newBuilder()
75+
.column("pk", DataTypes.INT())
76+
.column("str_col", DataTypes.STRING())
77+
.primaryKey("pk")
78+
.option("bucket", "1")
79+
.build();
80+
catalog.createTable(identifier("CastSuccessTable"), schema, true);
81+
82+
// Write data with numeric strings that can be cast to INT
83+
FileStoreTable table = getTable(identifier("CastSuccessTable"));
84+
write(
85+
table,
86+
GenericRow.of(1, BinaryString.fromString("100")),
87+
GenericRow.of(2, BinaryString.fromString("200")),
88+
GenericRow.of(3, BinaryString.fromString("300")));
89+
90+
// Alter table: change column type from STRING to INT
91+
catalog.alterTable(
92+
identifier("CastSuccessTable"),
93+
Collections.singletonList(
94+
SchemaChange.updateColumnType("str_col", DataTypes.INT())),
95+
false);
96+
97+
// Read should succeed
98+
FileStoreTable alteredTable = getTable(identifier("CastSuccessTable"));
99+
read(alteredTable);
100+
}
101+
}

0 commit comments

Comments
 (0)