Skip to content

Commit 2405204

Browse files
committed
fix(core): cluster A — span() output column type for date/time UDT
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.
1 parent ef9f11d commit 2405204

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747
import org.apache.calcite.sql.type.SqlTypeUtil;
4848
import org.checkerframework.checker.nullness.qual.Nullable;
4949
import org.opensearch.analytics.schema.BinaryType;
50+
import org.opensearch.analytics.schema.DateOnlyType;
5051
import org.opensearch.analytics.schema.IpType;
52+
import org.opensearch.analytics.schema.TimeOnlyType;
5153
import org.opensearch.sql.calcite.type.AbstractExprRelDataType;
5254
import org.opensearch.sql.calcite.type.ExprBinaryType;
5355
import org.opensearch.sql.calcite.type.ExprDateType;
@@ -293,6 +295,14 @@ public static ExprType convertAnalyticsEngineRelDataTypeToExprType(RelDataType t
293295
if (type instanceof BinaryType) {
294296
return BINARY;
295297
}
298+
// Sandbox UDT markers for format-classified date columns (Timestamp(ms)-backed wire,
299+
// user-visible label downgraded to date / time).
300+
if (type instanceof DateOnlyType) {
301+
return DATE;
302+
}
303+
if (type instanceof TimeOnlyType) {
304+
return TIME;
305+
}
296306
return convertRelDataTypeToExprType(type);
297307
}
298308

core/src/main/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngine.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import org.opensearch.analytics.exec.QueryPlanExecutor;
2121
import org.opensearch.analytics.exec.profile.ProfiledResult;
2222
import org.opensearch.analytics.schema.BinaryType;
23+
import org.opensearch.analytics.schema.DateOnlyType;
2324
import org.opensearch.analytics.schema.IpType;
25+
import org.opensearch.analytics.schema.TimeOnlyType;
2426
import org.opensearch.common.network.InetAddresses;
2527
import org.opensearch.core.action.ActionListener;
2628
import org.opensearch.sql.ast.statement.ExplainMode;
@@ -54,6 +56,11 @@ public class AnalyticsExecutionEngine implements ExecutionEngine {
5456
private static final Pattern EPOCH_DATE_TIME_PREFIX =
5557
Pattern.compile("^1970-01-01[ T](\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$");
5658

59+
// DATE-typed columns whose wire is Timestamp(ms) arrive as "YYYY-MM-DD HH:mm:ss";
60+
// when the column carries a DateOnlyType marker we strip the time suffix.
61+
private static final Pattern DATE_WITH_MIDNIGHT_TIME =
62+
Pattern.compile("^(\\d{4}-\\d{2}-\\d{2})[ T]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?$");
63+
5764
private final QueryPlanExecutor<RelNode, Iterable<Object[]>> planExecutor;
5865

5966
public AnalyticsExecutionEngine(QueryPlanExecutor<RelNode, Iterable<Object[]>> planExecutor) {
@@ -241,6 +248,20 @@ private static ExprValue toExprValue(Object value, RelDataType type) {
241248
return ExprValueUtils.stringValue(Base64.getEncoder().encodeToString(bytes));
242249
}
243250
}
251+
// span(date-typed) returns Timestamp(ms) wire with midnight time; render as YYYY-MM-DD only.
252+
if (type instanceof DateOnlyType && value instanceof String s) {
253+
var m = DATE_WITH_MIDNIGHT_TIME.matcher(s);
254+
if (m.matches()) {
255+
return ExprValueUtils.stringValue(m.group(1));
256+
}
257+
}
258+
// span(time-typed) returns Timestamp(ms) wire with 1970-01-01 prefix; render as HH:mm:ss only.
259+
if (type instanceof TimeOnlyType && value instanceof String s) {
260+
var m = EPOCH_DATE_TIME_PREFIX.matcher(s);
261+
if (m.matches()) {
262+
return ExprValueUtils.stringValue(m.group(1));
263+
}
264+
}
244265
// List elements that look like a sentinel-epoch-prefixed time render as HH:mm:ss only.
245266
if (value instanceof List<?> list) {
246267
return ExprValueUtils.collectionValue(stripEpochDatePrefixInList(list));

core/src/test/java/org/opensearch/sql/executor/analytics/AnalyticsExecutionEngineTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import org.junit.jupiter.api.Test;
3131
import org.opensearch.analytics.exec.QueryPlanExecutor;
3232
import org.opensearch.analytics.schema.BinaryType;
33+
import org.opensearch.analytics.schema.DateOnlyType;
3334
import org.opensearch.analytics.schema.IpType;
35+
import org.opensearch.analytics.schema.TimeOnlyType;
3436
import org.opensearch.core.action.ActionListener;
3537
import org.opensearch.sql.calcite.CalcitePlanContext;
3638
import org.opensearch.sql.calcite.SysLimit;
@@ -237,6 +239,40 @@ void executeRelNode_binaryColumnRendersAsBase64() {
237239
"byte[] should base64-encode to match OpenSearch binary wire format. " + dump);
238240
}
239241

242+
/**
243+
* DateOnlyType column value "YYYY-MM-DD HH:MM:SS" → schema "date" + value stripped to YYYY-MM-DD.
244+
*/
245+
@Test
246+
void executeRelNode_dateOnlyTypeStripsTimeSuffix() {
247+
RelNode relNode = mockRelNodeWithType("d", new DateOnlyType(RelDataTypeSystem.DEFAULT, true));
248+
Iterable<Object[]> rows = Collections.singletonList(new Object[] {"1984-04-12 00:00:00"});
249+
stubExecutorWith(relNode, rows);
250+
251+
QueryResponse response = executeAndCapture(relNode);
252+
String dump = dumpResponse(response);
253+
254+
assertEquals(ExprCoreType.DATE, response.getSchema().getColumns().get(0).getExprType(), dump);
255+
assertEquals(
256+
"1984-04-12", response.getResults().get(0).tupleValue().get("d").stringValue(), dump);
257+
}
258+
259+
/**
260+
* TimeOnlyType column value "1970-01-01 HH:MM:SS" → schema "time" + value stripped to HH:MM:SS.
261+
*/
262+
@Test
263+
void executeRelNode_timeOnlyTypeStripsEpochDatePrefix() {
264+
RelNode relNode = mockRelNodeWithType("t", new TimeOnlyType(RelDataTypeSystem.DEFAULT, true));
265+
Iterable<Object[]> rows = Collections.singletonList(new Object[] {"1970-01-01 09:00:00"});
266+
stubExecutorWith(relNode, rows);
267+
268+
QueryResponse response = executeAndCapture(relNode);
269+
String dump = dumpResponse(response);
270+
271+
assertEquals(ExprCoreType.TIME, response.getSchema().getColumns().get(0).getExprType(), dump);
272+
assertEquals(
273+
"09:00:00", response.getResults().get(0).tupleValue().get("t").stringValue(), dump);
274+
}
275+
240276
/** TIME-typed list elements arrive as "1970-01-01[ T]HH:mm:ss[.frac]" — strip the prefix. */
241277
@Test
242278
void executeRelNode_listOfStringStripsEpochDatePrefix() {

0 commit comments

Comments
 (0)