Skip to content

Commit 688e8d5

Browse files
morningmanclaude
andcommitted
[fix](fe) Reject a DEFAULT(column) whose writer type differs from its read type
### What problem does this PR solve? Issue Number: None Related PR: #66538 Problem Summary: Review follow-up. The previous commits typed the default that replaces a bound DEFAULT(column) as its pinned writer column. That type differs from the read column's only when the table changed outside Doris after Doris cached its read schema, e.g. an INT column promoted to BIGINT whose new write-default is 4900000001. The expressions around DEFAULT(column) were already bound against the read type and the rewriter keeps their signatures, so in UPDATE t SET ... WHERE IF(TRUE, DEFAULT(score), 0) = 4900000001 the IF keeps its INT result type and constant folding casts 4900000001 back to INT: NULL in non-strict mode, so no row matches, and an overflow error in strict mode. A substituted value cannot repair the types its consumers were bound with, so the rewrite now fails such a DEFAULT and asks to refresh the table. When the two types agree, which is every case without a type change made outside Doris, nothing changes. ### Release note None ### Check List (For Author) - Test: Unit Test - RewriteDefaultExpressionTest: a DEFAULT whose writer type differs is rejected, directly and under an IF bound to the read type - ConnectorWriteSchemaUtilsTest, InsertUtilsTest, ExternalRowLevelUpdatePlanBuilderTest, ExternalRowLevelMergePlanBuilderTest, ChangelogRowLevelDmlTransformTest, PluginDrivenExternalTableTest - Behavior changed: No - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent f1287fd commit 688e8d5

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

‎fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/RewriteDefaultExpression.java‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ private static Expression rewrite(ExpressionMatchingContext<Default> context) {
8181
}
8282

8383
Column column = columnOpt.get();
84+
DataType targetType = DataType.fromCatalogType(column.getType());
8485
if (column.isGeneratedColumn()) {
8586
throw new AnalysisException("DEFAULT cannot be used on generated column '"
8687
+ column.getName() + "'");
@@ -93,10 +94,16 @@ private static Expression rewrite(ExpressionMatchingContext<Default> context) {
9394
column = pinnedWriteColumn(context.cascadesContext.getStatementContext(), originalTable.get(), column)
9495
.orElseThrow(() -> new AnalysisException("DEFAULT(" + slotRef.toSql()
9596
+ ") must reference a column of the table that this statement writes"));
97+
// The expressions around DEFAULT(column) were bound against the read column, so a default of another type
98+
// cannot stand in for it. The types differ when the table changed outside Doris after its read schema was
99+
// cached, e.g. by a type promotion.
100+
DataType writeType = DataType.fromCatalogType(column.getType());
101+
if (!writeType.equals(targetType)) {
102+
throw new AnalysisException("DEFAULT(" + slotRef.toSql() + ") reads the column as "
103+
+ targetType.toSql() + " but the table now writes it as " + writeType.toSql()
104+
+ "; the table changed outside Doris, refresh it and retry");
105+
}
96106
}
97-
// The pinned writer column can be wider than the read column the slot was bound with, e.g. after a type
98-
// promotion committed outside Doris, and the default is a value of the writer column.
99-
DataType targetType = DataType.fromCatalogType(column.getType());
100107

101108
String defaultValueSql = column.getDefaultValueSql();
102109
if (defaultValueSql == null) {

‎fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/RewriteDefaultExpressionTest.java‎

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import org.apache.doris.nereids.trees.expressions.ExprId;
2828
import org.apache.doris.nereids.trees.expressions.Expression;
2929
import org.apache.doris.nereids.trees.expressions.SlotReference;
30+
import org.apache.doris.nereids.trees.expressions.functions.scalar.If;
31+
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
32+
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
3033
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
31-
import org.apache.doris.nereids.types.BigIntType;
3234
import org.apache.doris.nereids.types.IntegerType;
3335
import org.apache.doris.nereids.util.TypeCoercionUtils;
3436

@@ -62,18 +64,32 @@ public void writeOnlyDefaultResolvesAgainstPinnedWriterSchema() {
6264
}
6365

6466
@Test
65-
public void writeOnlyDefaultTakesTheTypeOfThePinnedWriterColumn() {
67+
public void writeOnlyDefaultOfAWriterColumnOfAnotherTypeIsRejected() {
6668
// The read schema can still hold the INT column after it was promoted to BIGINT outside Doris, while the
6769
// writer schema pinned for the statement has the BIGINT column and its new default, which an INT cannot hold.
6870
SlotReference score = scoreSlot(true);
6971
pinWriterColumn(PrimitiveType.BIGINT, "4900000001");
7072

71-
Expression rewritten = executor.rewrite(new Default(score), context);
73+
AnalysisException e = Assertions.assertThrows(AnalysisException.class,
74+
() -> executor.rewrite(new Default(score), context));
7275

73-
Assertions.assertEquals(
74-
TypeCoercionUtils.castIfNotSameType(PARSER.parseExpression("4900000001"), BigIntType.INSTANCE),
75-
rewritten);
76-
Assertions.assertEquals(BigIntType.INSTANCE, rewritten.getDataType());
76+
Assertions.assertTrue(e.getMessage().contains("DEFAULT(score) reads the column as INT but the table now writes"
77+
+ " it as BIGINT; the table changed outside Doris, refresh it and retry"), e.getMessage());
78+
}
79+
80+
@Test
81+
public void writeOnlyDefaultOfAnotherTypeIsRejectedInsideAnExpressionBoundToTheReadType() {
82+
// UPDATE t ... WHERE IF(TRUE, DEFAULT(score), 0) = 4900000001: IF was bound with the INT of the read column,
83+
// so substituting the BIGINT default would later fold into CAST(4900000001 AS INT), NULL in non-strict mode.
84+
SlotReference score = scoreSlot(true);
85+
pinWriterColumn(PrimitiveType.BIGINT, "4900000001");
86+
If ifExpr = new If(BooleanLiteral.TRUE, new Default(score), new IntegerLiteral(0));
87+
Assertions.assertEquals(IntegerType.INSTANCE, ifExpr.getDataType());
88+
89+
AnalysisException e = Assertions.assertThrows(AnalysisException.class,
90+
() -> executor.rewrite(ifExpr, context));
91+
92+
Assertions.assertTrue(e.getMessage().contains("refresh it and retry"), e.getMessage());
7793
}
7894

7995
@Test

0 commit comments

Comments
 (0)