From c24b2df5402e59b284ef5b7a629b0aaadc53363b Mon Sep 17 00:00:00 2001 From: Vinay Krishna Pudyodu Date: Sun, 7 Jun 2026 09:19:16 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix(core):=20cluster=20D=20=E2=80=94=20TIME?= =?UTF-8?q?-typed=20list=20elements=20format=20as=20HH:mm:ss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to OpenSearch fix/ai/datetime-clusters @ 9009736c2dc. list() now returns "HH:mm:ss[.fraction]" without the 1970-01-01 epoch-date prefix. The analytics-engine path rewrites PPL list() to DataFusion's list_merge, so the legacy ListAggFunction never fires. Instead, AnalyticsExecutionEngine now post-processes List-typed cells in the result conversion: when an element string matches "1970-01-01[ T]HH:mm:ss[.fraction]", only the time portion is kept. Scalar cells are untouched, preserving the wider timestamp-stringification regression baseline. Signed-off-by: Vinay Krishna Pudyodu --- .../analytics/AnalyticsExecutionEngine.java | 38 +++++++++++-------- .../AnalyticsExecutionEngineTest.java | 29 ++++++++++++++ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java b/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java index 733c603a76..4f6918bcf1 100644 --- a/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java +++ b/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java @@ -12,6 +12,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.type.RelDataType; @@ -47,6 +48,11 @@ */ public class AnalyticsExecutionEngine implements ExecutionEngine { + // TIME-typed list elements round-trip via Timestamp and bypass ArrowValues' scalar + // post-processing (see DataFusion list_merge), arriving as "1970-01-01[ T]HH:mm:ss[.frac]". + private static final Pattern EPOCH_DATE_TIME_PREFIX = + Pattern.compile("^1970-01-01[ T](\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$"); + private final QueryPlanExecutor> planExecutor; public AnalyticsExecutionEngine(QueryPlanExecutor> planExecutor) { @@ -206,21 +212,7 @@ private List convertRows(Iterable rows, List - *
  • {@link IpType} + {@code byte[]} → canonical address string (matches {@code - * IpFieldMapper}'s {@code valueFetcher} output). - *
  • {@link BinaryType} + {@code byte[]} → base64-encoded string (matches the OpenSearch - * {@code binary} field wire format). - *
  • Anything else → existing {@link ExprValueUtils#fromObjectValue} path. - * - * - *

    Without this dispatch, {@code fromObjectValue} throws {@code unsupported object class [B} on - * byte[] cells, and IP buffers leak through as raw 16-byte ipv4-mapped-ipv6 garbage. - */ + /** Renders byte[] as IP/base64 per UDT, strips epoch-date prefix from TIME list elements. */ private static ExprValue toExprValue(Object value, RelDataType type) { if (value instanceof byte[] bytes) { if (type instanceof IpType) { @@ -234,9 +226,25 @@ private static ExprValue toExprValue(Object value, RelDataType type) { return ExprValueUtils.stringValue(Base64.getEncoder().encodeToString(bytes)); } } + if (value instanceof List list) { + return ExprValueUtils.collectionValue(stripEpochDatePrefixInList(list)); + } return ExprValueUtils.fromObjectValue(value); } + private static List stripEpochDatePrefixInList(List list) { + List out = new ArrayList<>(list.size()); + for (Object element : list) { + if (element instanceof String s) { + var m = EPOCH_DATE_TIME_PREFIX.matcher(s); + out.add(m.matches() ? m.group(1) : s); + } else { + out.add(element); + } + } + return out; + } + private Schema buildSchema(List fields) { List columns = new ArrayList<>(); for (RelDataTypeField field : fields) { diff --git a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java index e759b128a4..ffdf3bf6d6 100644 --- a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java +++ b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java @@ -237,6 +237,35 @@ void executeRelNode_binaryColumnRendersAsBase64() { "byte[] should base64-encode to match OpenSearch binary wire format. " + dump); } + /** TIME-typed list elements arrive as "1970-01-01[ T]HH:mm:ss[.frac]" — strip the prefix. */ + @Test + void executeRelNode_listOfStringStripsEpochDatePrefix() { + SqlTypeFactoryImpl typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT); + RelDataType arrayOfVarchar = + typeFactory.createArrayType(typeFactory.createSqlType(SqlTypeName.VARCHAR), -1); + RelNode relNode = mockRelNodeWithType("time_list", arrayOfVarchar); + java.util.List input = + Arrays.asList( + "1970-01-01 19:36:22", + "1970-01-01T02:05:25", + "1970-01-01 12:34:56.123456789", + "2020-10-13 13:00:00", + "hello"); + stubExecutorWith(relNode, Collections.singletonList(new Object[] {input})); + + QueryResponse response = executeAndCapture(relNode); + String dump = dumpResponse(response); + + java.util.List result = + response.getResults().get(0).tupleValue().get("time_list").collectionValue().stream() + .map(org.opensearch.sql.data.model.ExprValue::stringValue) + .toList(); + assertEquals( + Arrays.asList("19:36:22", "02:05:25", "12:34:56.123456789", "2020-10-13 13:00:00", "hello"), + result, + dump); + } + @Test void executeRelNode_emptyResults() { RelNode relNode = mockRelNode("name", SqlTypeName.VARCHAR); From 0b44c49a516e85486dbe2a8d860791323a4dde62 Mon Sep 17 00:00:00 2001 From: Vinay Krishna Pudyodu Date: Sun, 7 Jun 2026 17:16:35 +0000 Subject: [PATCH 2/5] =?UTF-8?q?fix(core):=20cluster=20A=20=E2=80=94=20span?= =?UTF-8?q?()=20output=20column=20type=20for=20date/time=20UDT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recognize the new sandbox DateOnlyType / TimeOnlyType UDT markers in: - OpenSearchTypeFactory.convertAnalyticsEngineRelDataTypeToExprType: DateOnlyType → ExprCoreType.DATE, TimeOnlyType → ExprCoreType.TIME so the user-visible response schema labels span() bucket columns as `date` / `time` instead of `timestamp`. - AnalyticsExecutionEngine.toExprValue: when the column carries a DateOnlyType marker, strip the trailing ` HH:MM:SS` from the Timestamp(ms)-formatted wire value so dates render as `YYYY-MM-DD`. Symmetric handling for TimeOnlyType strips the `1970-01-01 ` prefix. Pairs with the sandbox schema-builder change in opensearch-project/OpenSearch@b69c5ff8888. Signed-off-by: Vinay Krishna Pudyodu --- .../calcite/utils/OpenSearchTypeFactory.java | 9 ++++++ .../analytics/AnalyticsExecutionEngine.java | 22 ++++++++++++- .../AnalyticsExecutionEngineTest.java | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java index 9c15c1485c..f365bfd965 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java @@ -47,7 +47,9 @@ import org.apache.calcite.sql.type.SqlTypeUtil; import org.checkerframework.checker.nullness.qual.Nullable; import org.opensearch.analytics.schema.BinaryType; +import org.opensearch.analytics.schema.DateOnlyType; import org.opensearch.analytics.schema.IpType; +import org.opensearch.analytics.schema.TimeOnlyType; import org.opensearch.sql.calcite.type.AbstractExprRelDataType; import org.opensearch.sql.calcite.type.ExprBinaryType; import org.opensearch.sql.calcite.type.ExprDateType; @@ -293,6 +295,13 @@ public static ExprType convertAnalyticsEngineRelDataTypeToExprType(RelDataType t if (type instanceof BinaryType) { return BINARY; } + // span() over date / time UDT — schema label is DATE / TIME, not TIMESTAMP. + if (type instanceof DateOnlyType) { + return DATE; + } + if (type instanceof TimeOnlyType) { + return TIME; + } return convertRelDataTypeToExprType(type); } diff --git a/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java b/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java index 4f6918bcf1..68dffacc2e 100644 --- a/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java +++ b/core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java @@ -20,7 +20,9 @@ import org.opensearch.analytics.exec.QueryPlanExecutor; import org.opensearch.analytics.exec.profile.ProfiledResult; import org.opensearch.analytics.schema.BinaryType; +import org.opensearch.analytics.schema.DateOnlyType; import org.opensearch.analytics.schema.IpType; +import org.opensearch.analytics.schema.TimeOnlyType; import org.opensearch.common.network.InetAddresses; import org.opensearch.core.action.ActionListener; import org.opensearch.sql.ast.statement.ExplainMode; @@ -53,6 +55,10 @@ public class AnalyticsExecutionEngine implements ExecutionEngine { private static final Pattern EPOCH_DATE_TIME_PREFIX = Pattern.compile("^1970-01-01[ T](\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$"); + // DateOnlyType wire is Timestamp(ms) at midnight — keep the date, drop the time. + private static final Pattern DATE_WITH_MIDNIGHT_TIME = + Pattern.compile("^(\\d{4}-\\d{2}-\\d{2})[ T]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?$"); + private final QueryPlanExecutor> planExecutor; public AnalyticsExecutionEngine(QueryPlanExecutor> planExecutor) { @@ -212,7 +218,7 @@ private List convertRows(Iterable rows, List list) { return ExprValueUtils.collectionValue(stripEpochDatePrefixInList(list)); } diff --git a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java index ffdf3bf6d6..eb5cb863dc 100644 --- a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java +++ b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java @@ -30,7 +30,9 @@ import org.junit.jupiter.api.Test; import org.opensearch.analytics.exec.QueryPlanExecutor; import org.opensearch.analytics.schema.BinaryType; +import org.opensearch.analytics.schema.DateOnlyType; import org.opensearch.analytics.schema.IpType; +import org.opensearch.analytics.schema.TimeOnlyType; import org.opensearch.core.action.ActionListener; import org.opensearch.sql.calcite.CalcitePlanContext; import org.opensearch.sql.calcite.SysLimit; @@ -237,6 +239,36 @@ void executeRelNode_binaryColumnRendersAsBase64() { "byte[] should base64-encode to match OpenSearch binary wire format. " + dump); } + /** DateOnlyType — schema reports DATE, value strips midnight suffix. */ + @Test + void executeRelNode_dateOnlyTypeStripsTimeSuffix() { + RelNode relNode = mockRelNodeWithType("d", new DateOnlyType(RelDataTypeSystem.DEFAULT, true)); + Iterable rows = Collections.singletonList(new Object[] {"1984-04-12 00:00:00"}); + stubExecutorWith(relNode, rows); + + QueryResponse response = executeAndCapture(relNode); + String dump = dumpResponse(response); + + assertEquals(ExprCoreType.DATE, response.getSchema().getColumns().get(0).getExprType(), dump); + assertEquals( + "1984-04-12", response.getResults().get(0).tupleValue().get("d").stringValue(), dump); + } + + /** TimeOnlyType — schema reports TIME, value strips 1970-01-01 prefix. */ + @Test + void executeRelNode_timeOnlyTypeStripsEpochDatePrefix() { + RelNode relNode = mockRelNodeWithType("t", new TimeOnlyType(RelDataTypeSystem.DEFAULT, true)); + Iterable rows = Collections.singletonList(new Object[] {"1970-01-01 09:00:00"}); + stubExecutorWith(relNode, rows); + + QueryResponse response = executeAndCapture(relNode); + String dump = dumpResponse(response); + + assertEquals(ExprCoreType.TIME, response.getSchema().getColumns().get(0).getExprType(), dump); + assertEquals( + "09:00:00", response.getResults().get(0).tupleValue().get("t").stringValue(), dump); + } + /** TIME-typed list elements arrive as "1970-01-01[ T]HH:mm:ss[.frac]" — strip the prefix. */ @Test void executeRelNode_listOfStringStripsEpochDatePrefix() { From 8f971d8fe21727630218466984dd25215312bff8 Mon Sep 17 00:00:00 2001 From: Vinay Krishna Pudyodu Date: Sun, 7 Jun 2026 15:39:32 -0700 Subject: [PATCH 3/5] Preserve DATE/TIME return type for ADDDATE/SUBDATE/ADDTIME/SUBTIME on date/time columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the analytics-engine route a 'date'/'time' column is a TIMESTAMP-backed DateOnlyType/TimeOnlyType marker, so operand-conditional return-type inference mis-read it as TIMESTAMP — ADDDATE(date_col, N) reported (and rendered) TIMESTAMP instead of DATE. Add isDateExprType/isTimeExprType helpers recognizing those markers (off the general conversion path, no substrait round-trip risk) and use them in AddSubDateFunction and PPLReturnTypes.TIME_APPLY_RETURN_TYPE. Fixes the 6 *Null column-operand cases end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Vinay Krishna Pudyodu --- .../calcite/utils/OpenSearchTypeFactory.java | 18 ++++++++++++++++++ .../sql/calcite/utils/PPLReturnTypes.java | 6 +++--- .../udf/datetime/AddSubDateFunction.java | 5 ++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java index f365bfd965..765ce004d8 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java @@ -279,6 +279,24 @@ public static ExprType convertRelDataTypeToExprType(RelDataType type) { return exprType; } + /** + * Whether {@code type} represents a DATE for the purpose of operand-conditional return-type + * inference (e.g. {@code ADDDATE(date, n)} returns DATE only when the base is a date). Recognizes + * both the SQL-plugin DATE UDT (via {@link #convertRelDataTypeToExprType}) and the + * analytics-route {@link DateOnlyType} column marker, which is {@code TIMESTAMP}-backed and would + * otherwise be misread as TIMESTAMP. Kept separate from {@link #convertRelDataTypeToExprType} so + * it isn't on the general planner path that round-trips through {@link + * #convertExprTypeToRelDataType}. + */ + public static boolean isDateExprType(RelDataType type) { + return type instanceof DateOnlyType || convertRelDataTypeToExprType(type) == ExprCoreType.DATE; + } + + /** TIME counterpart of {@link #isDateExprType} — recognizes the {@link TimeOnlyType} marker. */ + public static boolean isTimeExprType(RelDataType type) { + return type instanceof TimeOnlyType || convertRelDataTypeToExprType(type) == ExprCoreType.TIME; + } + /** * Result-schema-only variant of {@link #convertRelDataTypeToExprType} that recognizes the * analytics-engine {@link IpType} / {@link BinaryType} markers as {@link ExprCoreType#IP} / diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java index acf2a6b8eb..ffbf8e46de 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java @@ -12,7 +12,6 @@ import org.apache.calcite.sql.type.SqlReturnTypeInference; import org.apache.calcite.sql.type.SqlTypeTransforms; import org.apache.calcite.sql.type.SqlTypeUtil; -import org.opensearch.sql.data.type.ExprCoreType; /** * Return types used in PPL. This class complements the {@link @@ -36,8 +35,9 @@ private PPLReturnTypes() {} public static SqlReturnTypeInference TIME_APPLY_RETURN_TYPE = opBinding -> { RelDataType temporalType = opBinding.getOperandType(0); - if (ExprCoreType.TIME.equals( - OpenSearchTypeFactory.convertRelDataTypeToExprType(temporalType))) { + // isTimeExprType so a TimeOnlyType column (analytics-route TIMESTAMP-backed time marker) is + // recognized as TIME; otherwise ADDTIME/SUBTIME on a time column lose the TIME return type. + if (OpenSearchTypeFactory.isTimeExprType(temporalType)) { return UserDefinedFunctionUtils.NULLABLE_TIME_UDT; } return UserDefinedFunctionUtils.NULLABLE_TIMESTAMP_UDT; diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java index 9456e6b857..ed0336c5e8 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java @@ -63,7 +63,10 @@ public SqlReturnTypeInference getReturnTypeInference() { return opBinding -> { RelDataType temporalType = opBinding.getOperandType(0); RelDataType temporalDeltaType = opBinding.getOperandType(1); - if (OpenSearchTypeFactory.convertRelDataTypeToExprType(temporalType) == ExprCoreType.DATE + // isDateExprType (not convertRelDataTypeToExprType == DATE) so a DateOnlyType column — the + // analytics-route TIMESTAMP-backed date marker — is recognized as DATE; otherwise ADDDATE/ + // SUBDATE on a date column would mis-declare TIMESTAMP and lose the DATE return type. + if (OpenSearchTypeFactory.isDateExprType(temporalType) && SqlTypeFamily.NUMERIC.contains(temporalDeltaType)) { return NULLABLE_DATE_UDT; } else { From 2b8bb6163917eb55e67b83bc21f7c3776253b28a Mon Sep 17 00:00:00 2001 From: Vinay Krishna Pudyodu Date: Mon, 8 Jun 2026 08:21:51 +0000 Subject: [PATCH 4/5] Tighten ADDDATE/SUBDATE return-type helper comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comment trims on top of d12407b1a (Marc Handalian's cherry-pick) — collapse the multi-line Javadoc on isDateExprType/isTimeExprType to one-line case names, and drop the inline narration at the two call sites (PPLReturnTypes.TIME_APPLY_RETURN_TYPE, AddSubDateFunction#getReturnTypeInference) that restated the helper contract. Behavior unchanged. Signed-off-by: Vinay Krishna Pudyodu --- .../sql/calcite/utils/OpenSearchTypeFactory.java | 12 ++---------- .../opensearch/sql/calcite/utils/PPLReturnTypes.java | 2 -- .../function/udf/datetime/AddSubDateFunction.java | 3 --- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java index 765ce004d8..b8ed9b6cd7 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java @@ -279,20 +279,12 @@ public static ExprType convertRelDataTypeToExprType(RelDataType type) { return exprType; } - /** - * Whether {@code type} represents a DATE for the purpose of operand-conditional return-type - * inference (e.g. {@code ADDDATE(date, n)} returns DATE only when the base is a date). Recognizes - * both the SQL-plugin DATE UDT (via {@link #convertRelDataTypeToExprType}) and the - * analytics-route {@link DateOnlyType} column marker, which is {@code TIMESTAMP}-backed and would - * otherwise be misread as TIMESTAMP. Kept separate from {@link #convertRelDataTypeToExprType} so - * it isn't on the general planner path that round-trips through {@link - * #convertExprTypeToRelDataType}. - */ + /** DATE check for return-type inference; recognizes the analytics-route {@link DateOnlyType}. */ public static boolean isDateExprType(RelDataType type) { return type instanceof DateOnlyType || convertRelDataTypeToExprType(type) == ExprCoreType.DATE; } - /** TIME counterpart of {@link #isDateExprType} — recognizes the {@link TimeOnlyType} marker. */ + /** TIME counterpart of {@link #isDateExprType}; recognizes {@link TimeOnlyType}. */ public static boolean isTimeExprType(RelDataType type) { return type instanceof TimeOnlyType || convertRelDataTypeToExprType(type) == ExprCoreType.TIME; } diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java index ffbf8e46de..ce7dd408e7 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLReturnTypes.java @@ -35,8 +35,6 @@ private PPLReturnTypes() {} public static SqlReturnTypeInference TIME_APPLY_RETURN_TYPE = opBinding -> { RelDataType temporalType = opBinding.getOperandType(0); - // isTimeExprType so a TimeOnlyType column (analytics-route TIMESTAMP-backed time marker) is - // recognized as TIME; otherwise ADDTIME/SUBTIME on a time column lose the TIME return type. if (OpenSearchTypeFactory.isTimeExprType(temporalType)) { return UserDefinedFunctionUtils.NULLABLE_TIME_UDT; } diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java index ed0336c5e8..2c040918e8 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/datetime/AddSubDateFunction.java @@ -63,9 +63,6 @@ public SqlReturnTypeInference getReturnTypeInference() { return opBinding -> { RelDataType temporalType = opBinding.getOperandType(0); RelDataType temporalDeltaType = opBinding.getOperandType(1); - // isDateExprType (not convertRelDataTypeToExprType == DATE) so a DateOnlyType column — the - // analytics-route TIMESTAMP-backed date marker — is recognized as DATE; otherwise ADDDATE/ - // SUBDATE on a date column would mis-declare TIMESTAMP and lose the DATE return type. if (OpenSearchTypeFactory.isDateExprType(temporalType) && SqlTypeFamily.NUMERIC.contains(temporalDeltaType)) { return NULLABLE_DATE_UDT; From 0f781da9de335b66c552b8365feb94adbe7e40f5 Mon Sep 17 00:00:00 2001 From: Vinay Krishna Pudyodu Date: Tue, 9 Jun 2026 01:21:51 +0000 Subject: [PATCH 5/5] Fix tests Signed-off-by: Vinay Krishna Pudyodu --- .../executor/analytics/AnalyticsExecutionEngineTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java index eb5cb863dc..08b8804846 100644 --- a/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java +++ b/core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java @@ -242,7 +242,8 @@ void executeRelNode_binaryColumnRendersAsBase64() { /** DateOnlyType — schema reports DATE, value strips midnight suffix. */ @Test void executeRelNode_dateOnlyTypeStripsTimeSuffix() { - RelNode relNode = mockRelNodeWithType("d", new DateOnlyType(RelDataTypeSystem.DEFAULT, true)); + RelNode relNode = + mockRelNodeWithType("d", new DateOnlyType(RelDataTypeSystem.DEFAULT, true, 3)); Iterable rows = Collections.singletonList(new Object[] {"1984-04-12 00:00:00"}); stubExecutorWith(relNode, rows); @@ -257,7 +258,8 @@ void executeRelNode_dateOnlyTypeStripsTimeSuffix() { /** TimeOnlyType — schema reports TIME, value strips 1970-01-01 prefix. */ @Test void executeRelNode_timeOnlyTypeStripsEpochDatePrefix() { - RelNode relNode = mockRelNodeWithType("t", new TimeOnlyType(RelDataTypeSystem.DEFAULT, true)); + RelNode relNode = + mockRelNodeWithType("t", new TimeOnlyType(RelDataTypeSystem.DEFAULT, true, 3)); Iterable rows = Collections.singletonList(new Object[] {"1970-01-01 09:00:00"}); stubExecutorWith(relNode, rows);