diff --git a/core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java b/core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java index d8bea5e3371..4d86614895b 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java +++ b/core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java @@ -126,6 +126,9 @@ public RexNode makeCast( // ImmutableList.of(exp, makeZeroLiteral(sourceType))); } } else if (OpenSearchTypeFactory.isUserDefinedType(type)) { + if (RexLiteral.isNullLiteral(exp)) { + return super.makeCast(pos, type, exp, matchNullability, safe, format); + } var udt = ((AbstractExprRelDataType) type).getUdt(); var argExprType = OpenSearchTypeFactory.convertRelDataTypeToExprType(sourceType); return switch (udt) { diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLAppendCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLAppendCommandIT.java index d971a6f3cb1..bc1e11a908c 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLAppendCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLAppendCommandIT.java @@ -7,6 +7,7 @@ import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT; import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK; +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_WEBLOGS; import static org.opensearch.sql.util.MatcherUtils.rows; import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; @@ -28,6 +29,7 @@ public void init() throws Exception { enableCalcite(); loadIndex(Index.ACCOUNT); loadIndex(Index.BANK); + loadIndex(Index.WEBLOG); } @Test @@ -236,4 +238,39 @@ public void testAppendWithConflictTypeColumn() throws IOException { rows(null, null, "NM", 412d), rows(null, null, "AZ", 414d)); } + + @Test + public void testAppendSchemaMergeWithTimestampUDT() throws IOException { + JSONObject actual = + executeQuery( + String.format( + Locale.ROOT, + "source=%s | fields account_number, age | append [ source=%s | fields" + + " account_number, age, birthdate ] | where isnotnull(birthdate) and" + + " account_number > 30", + TEST_INDEX_ACCOUNT, + TEST_INDEX_BANK)); + verifySchemaInOrder( + actual, + schema("account_number", "bigint"), + schema("age", "bigint"), + schema("age0", "int"), + schema("birthdate", "string")); + verifyDataRows(actual, rows(32, null, 34, "2018-08-11 00:00:00")); + } + + @Test + public void testAppendSchemaMergeWithIpUDT() throws IOException { + JSONObject actual = + executeQuery( + String.format( + Locale.ROOT, + "source=%s | fields account_number, age | append [ source=%s | fields host ] |" + + " where cidrmatch(host, '0.0.0.0/24')", + TEST_INDEX_ACCOUNT, + TEST_INDEX_WEBLOGS)); + verifySchemaInOrder( + actual, schema("account_number", "bigint"), schema("age", "bigint"), schema("host", "ip")); + verifyDataRows(actual, rows(null, null, "0.0.0.2")); + } } diff --git a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/issues/4383.yml b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/issues/4383.yml new file mode 100644 index 00000000000..6ca48dafe52 --- /dev/null +++ b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/issues/4383.yml @@ -0,0 +1,62 @@ +setup: + - do: + query.settings: + body: + transient: + plugins.calcite.enabled : true + +--- +teardown: + - do: + query.settings: + body: + transient: + plugins.calcite.enabled : false + +--- +"Append UDT fields should merge schema successfully": + - skip: + features: + - headers + - allowed_warnings + - do: + indices.create: + index: log-test1 + body: + mappings: + properties: + "timestamp": + type: date + - do: + indices.create: + index: log-test2 + body: + mappings: + properties: + "host": + type: ip + + - do: + bulk: + index: log-test1 + refresh: true + body: + - '{"index": {}}' + - '{ "timestamp" : "2025-09-04T16:15:00.000Z" }' + - do: + bulk: + index: log-test2 + refresh: true + body: + - '{"index": {}}' + - '{ "host" : "0.0.0.2" }' + + - do: + headers: + Content-Type: 'application/json' + ppl: + body: + query: source=log-test1 | append [ source=log-test2 ] + + - match: { total: 2 } + - match: { datarows: [["2025-09-04 16:15:00", null], [null, "0.0.0.2"]] }