Skip to content

Commit ef9f11d

Browse files
committed
fix(core): cluster D — TIME-typed list elements format as HH:mm:ss
Companion to OpenSearch fix/ai/datetime-clusters @ 9009736c2dc. list(<TIME-typed field>) 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 <vinkrish.neo@gmail.com>
1 parent cf14aba commit ef9f11d

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.LinkedHashMap;
1313
import java.util.List;
1414
import java.util.Map;
15+
import java.util.regex.Pattern;
1516
import org.apache.calcite.plan.RelOptUtil;
1617
import org.apache.calcite.rel.RelNode;
1718
import org.apache.calcite.rel.type.RelDataType;
@@ -47,6 +48,12 @@
4748
*/
4849
public class AnalyticsExecutionEngine implements ExecutionEngine {
4950

51+
// TIME-typed columns round-trip through Timestamp and arrive in list elements as
52+
// "1970-01-01[ T]HH:mm:ss[.fraction]"; analytics-engine post-processes scalars but
53+
// list-aggregation elements bypass that path (see list_merge in DataFusion).
54+
private static final Pattern EPOCH_DATE_TIME_PREFIX =
55+
Pattern.compile("^1970-01-01[ T](\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$");
56+
5057
private final QueryPlanExecutor<RelNode, Iterable<Object[]>> planExecutor;
5158

5259
public AnalyticsExecutionEngine(QueryPlanExecutor<RelNode, Iterable<Object[]>> planExecutor) {
@@ -234,9 +241,30 @@ private static ExprValue toExprValue(Object value, RelDataType type) {
234241
return ExprValueUtils.stringValue(Base64.getEncoder().encodeToString(bytes));
235242
}
236243
}
244+
// List elements that look like a sentinel-epoch-prefixed time render as HH:mm:ss only.
245+
if (value instanceof List<?> list) {
246+
return ExprValueUtils.collectionValue(stripEpochDatePrefixInList(list));
247+
}
237248
return ExprValueUtils.fromObjectValue(value);
238249
}
239250

251+
/**
252+
* Returns a copy of {@code list} with each "1970-01-01[ T]HH:mm:ss[.fraction]" string replaced by
253+
* the time portion only; non-matching elements pass through unchanged.
254+
*/
255+
private static List<Object> stripEpochDatePrefixInList(List<?> list) {
256+
List<Object> out = new ArrayList<>(list.size());
257+
for (Object element : list) {
258+
if (element instanceof String s) {
259+
var m = EPOCH_DATE_TIME_PREFIX.matcher(s);
260+
out.add(m.matches() ? m.group(1) : s);
261+
} else {
262+
out.add(element);
263+
}
264+
}
265+
return out;
266+
}
267+
240268
private Schema buildSchema(List<RelDataTypeField> fields) {
241269
List<Schema.Column> columns = new ArrayList<>();
242270
for (RelDataTypeField field : fields) {

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,38 @@ void executeRelNode_binaryColumnRendersAsBase64() {
237237
"byte[] should base64-encode to match OpenSearch binary wire format. " + dump);
238238
}
239239

240+
/** TIME-typed list elements arrive as "1970-01-01[ T]HH:mm:ss[.frac]" — strip the prefix. */
241+
@Test
242+
void executeRelNode_listOfStringStripsEpochDatePrefix() {
243+
SqlTypeFactoryImpl typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
244+
RelDataType varchar = typeFactory.createSqlType(SqlTypeName.VARCHAR);
245+
RelDataType arrayOfVarchar = typeFactory.createArrayType(varchar, -1);
246+
RelDataType rowType = typeFactory.builder().add("time_list", arrayOfVarchar).build();
247+
RelNode relNode = mock(RelNode.class);
248+
when(relNode.getRowType()).thenReturn(rowType);
249+
java.util.List<String> input =
250+
Arrays.asList(
251+
"1970-01-01 19:36:22",
252+
"1970-01-01T02:05:25",
253+
"1970-01-01 12:34:56.123456789",
254+
"2020-10-13 13:00:00",
255+
"hello");
256+
Iterable<Object[]> rows = Collections.singletonList(new Object[] {input});
257+
stubExecutorWith(relNode, rows);
258+
259+
QueryResponse response = executeAndCapture(relNode);
260+
String dump = dumpResponse(response);
261+
262+
java.util.List<String> result =
263+
response.getResults().get(0).tupleValue().get("time_list").collectionValue().stream()
264+
.map(org.opensearch.sql.data.model.ExprValue::stringValue)
265+
.toList();
266+
assertEquals(
267+
Arrays.asList("19:36:22", "02:05:25", "12:34:56.123456789", "2020-10-13 13:00:00", "hello"),
268+
result,
269+
dump);
270+
}
271+
240272
@Test
241273
void executeRelNode_emptyResults() {
242274
RelNode relNode = mockRelNode("name", SqlTypeName.VARCHAR);

0 commit comments

Comments
 (0)