Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.opensearch.sql.ast.tree.Limit;
import org.opensearch.sql.ast.tree.Lookup;
import org.opensearch.sql.ast.tree.ML;
import org.opensearch.sql.ast.tree.MakeResults;
import org.opensearch.sql.ast.tree.Multisearch;
import org.opensearch.sql.ast.tree.MvCombine;
import org.opensearch.sql.ast.tree.MvExpand;
Expand Down Expand Up @@ -561,6 +562,11 @@ public LogicalPlan visitNoMv(NoMv node, AnalysisContext context) {
throw getOnlyForCalciteException("nomv");
}

@Override
public LogicalPlan visitMakeResults(MakeResults node, AnalysisContext context) {
throw getOnlyForCalciteException("makeresults");
}

@Override
public LogicalPlan visitMvExpand(MvExpand node, AnalysisContext context) {
throw getOnlyForCalciteException("mvexpand");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.opensearch.sql.ast.tree.Limit;
import org.opensearch.sql.ast.tree.Lookup;
import org.opensearch.sql.ast.tree.ML;
import org.opensearch.sql.ast.tree.MakeResults;
import org.opensearch.sql.ast.tree.Multisearch;
import org.opensearch.sql.ast.tree.MvCombine;
import org.opensearch.sql.ast.tree.MvExpand;
Expand Down Expand Up @@ -334,6 +335,10 @@ public T visitValues(Values node, C context) {
return visitChildren(node, context);
}

public T visitMakeResults(MakeResults node, C context) {
return visitChildren(node, context);
}

public T visitAlias(Alias node, C context) {
return visitChildren(node, context);
}
Expand Down
47 changes: 47 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/MakeResults.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.ast.tree;

import com.google.common.collect.ImmutableList;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.opensearch.sql.ast.AbstractNodeVisitor;
import org.opensearch.sql.ast.Node;

/**
* AST node for the {@code makeresults} leading command (count path). Generates {@code count}
* in-memory rows, each carrying a single {@code @timestamp} column set to query time.
*
* <p>The {@code format=csv|json data="..."} form is parsed into a shared {@link Values} node
* instead (see {@code MakeResultsDataParser}), so inline literal rows flow through the common
* {@code visitValues} builder.
*/
@ToString
@Getter
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
public class MakeResults extends UnresolvedPlan {

private final int count;

@Override
public UnresolvedPlan attach(UnresolvedPlan child) {
throw new UnsupportedOperationException("MakeResults node is supposed to have no child node");
}

@Override
public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) {
return nodeVisitor.visitMakeResults(this, context);
}

@Override
public List<? extends Node> getChild() {
return ImmutableList.of();
}
}
44 changes: 42 additions & 2 deletions core/src/main/java/org/opensearch/sql/ast/tree/Values.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,61 @@
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.opensearch.sql.ast.AbstractNodeVisitor;
import org.opensearch.sql.ast.Node;
import org.opensearch.sql.ast.expression.Literal;
import org.opensearch.sql.data.type.ExprCoreType;

/** AST node class for a sequence of literal values. */
@ToString
@Getter
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
public class Values extends UnresolvedPlan {

private final List<List<Literal>> values;

/**
* Optional explicit column names (e.g. from {@code makeresults data=}). When {@code null},
* columns are positional/auto-named.
*/
private final List<String> columnNames;

/**
* Optional explicit column types (e.g. from {@code makeresults data=}), authoritative for the
* schema. Required to type a zero-row relation (header-only CSV / empty JSON array), where there
* are no {@link Literal}s to infer from. When {@code null}, types are inferred from the literals.
*/
private final List<ExprCoreType> columnTypes;

/**
* When {@code true}, an implicit {@code @timestamp = NOW()} column is prepended to the relation
* (e.g. from {@code makeresults format=json data=}, where each JSON object is treated as an event
* carrying a query-time timestamp). CSV {@code data=} leaves this {@code false} (pure table, no
* timestamp), and subsearch/dual-table callers never set it.
*/
private final boolean withImplicitTimestamp;

public Values(List<List<Literal>> values) {
this(values, null, null);
}

public Values(
List<List<Literal>> values, List<String> columnNames, List<ExprCoreType> columnTypes) {
this(values, columnNames, columnTypes, false);
}

public Values(
List<List<Literal>> values,
List<String> columnNames,
List<ExprCoreType> columnTypes,
boolean withImplicitTimestamp) {
this.values = values;
this.columnNames = columnNames;
this.columnTypes = columnTypes;
this.withImplicitTimestamp = withImplicitTimestamp;
}

@Override
public UnresolvedPlan attach(UnresolvedPlan child) {
throw new UnsupportedOperationException("Values node is supposed to have no child node");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
import org.opensearch.sql.ast.expression.AllFieldsExcludeMeta;
import org.opensearch.sql.ast.expression.Argument;
import org.opensearch.sql.ast.expression.Argument.ArgumentMap;
import org.opensearch.sql.ast.expression.DataType;
import org.opensearch.sql.ast.expression.Field;
import org.opensearch.sql.ast.expression.Function;
import org.opensearch.sql.ast.expression.Let;
Expand Down Expand Up @@ -139,6 +140,7 @@
import org.opensearch.sql.ast.tree.Lookup;
import org.opensearch.sql.ast.tree.Lookup.OutputStrategy;
import org.opensearch.sql.ast.tree.ML;
import org.opensearch.sql.ast.tree.MakeResults;
import org.opensearch.sql.ast.tree.Multisearch;
import org.opensearch.sql.ast.tree.MvCombine;
import org.opensearch.sql.ast.tree.MvExpand;
Expand Down Expand Up @@ -176,6 +178,7 @@
import org.opensearch.sql.calcite.plan.rel.LogicalSystemLimit.SystemLimitType;
import org.opensearch.sql.calcite.utils.BinUtils;
import org.opensearch.sql.calcite.utils.JoinAndLookupUtils;
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
import org.opensearch.sql.calcite.utils.PPLHintUtils;
import org.opensearch.sql.calcite.utils.PlanUtils;
import org.opensearch.sql.calcite.utils.TimewrapUtils;
Expand All @@ -185,6 +188,7 @@
import org.opensearch.sql.common.error.ErrorReport;
import org.opensearch.sql.common.patterns.PatternUtils;
import org.opensearch.sql.common.utils.StringUtils;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.datasource.DataSourceService;
import org.opensearch.sql.exception.CalciteUnsupportedException;
import org.opensearch.sql.exception.SemanticCheckException;
Expand Down Expand Up @@ -4459,17 +4463,154 @@ public RelNode visitMvExpand(MvExpand mvExpand, CalcitePlanContext context) {
@Override
public RelNode visitValues(Values values, CalcitePlanContext context) {
List<List<Literal>> rows = values.getValues();
if (rows == null || rows.isEmpty()) {
RelBuilder relBuilder = context.relBuilder;
boolean hasExplicitSchema = values.getColumnNames() != null || values.getColumnTypes() != null;
if (!hasExplicitSchema && (rows == null || rows.isEmpty())) {
// PPL empty subsearch (e.g., `... | append [ ]`): zero rows, no columns.
context.relBuilder.values(context.relBuilder.getTypeFactory().builder().build());
return context.relBuilder.peek();
relBuilder.values(relBuilder.getTypeFactory().builder().build());
return relBuilder.peek();
}
if (rows.size() == 1 && rows.get(0).isEmpty()) {
if (rows != null && rows.size() == 1 && rows.get(0).isEmpty()) {
// SQL FROM-less SELECT (dual table) encoded as Values([[]]): one-row relation for Project.
context.relBuilder.push(LogicalValues.createOneRow(context.relBuilder.getCluster()));
return context.relBuilder.peek();
relBuilder.push(LogicalValues.createOneRow(relBuilder.getCluster()));
return relBuilder.peek();
}
// Inline literal rows (e.g. `makeresults format=csv|json data=...`): build a typed
// LogicalValues
// with the given/derived schema, then project each column cast to the resolved type.
return buildLiteralValues(
relBuilder,
values.getColumnNames(),
values.getColumnTypes(),
rows,
values.isWithImplicitTimestamp());
}

/**
* Build a typed {@link LogicalValues} (+ a cast {@code Project}) from inline literal rows. Column
* names/types are taken from the explicit lists when provided (authoritative, and required to
* type a zero-row relation); otherwise names are positional and types are inferred from the
* literals.
*/
private RelNode buildLiteralValues(
RelBuilder relBuilder,
List<String> explicitNames,
List<ExprCoreType> explicitTypes,
List<List<Literal>> rows,
boolean withImplicitTimestamp) {
int nc;
if (explicitTypes != null) {
nc = explicitTypes.size();
} else if (explicitNames != null) {
nc = explicitNames.size();
} else {
nc = rows.isEmpty() ? 0 : rows.get(0).size();
}

List<String> names = new java.util.ArrayList<>();
for (int i = 0; i < nc; i++) {
names.add(explicitNames != null ? explicitNames.get(i) : "column_" + i);
}

List<ExprCoreType> types = new java.util.ArrayList<>();
for (int c = 0; c < nc; c++) {
if (explicitTypes != null) {
types.add(explicitTypes.get(c));
} else {
// infer from the first non-null literal in this column, defaulting to STRING.
ExprCoreType t = ExprCoreType.STRING;
for (List<Literal> row : rows) {
DataType dt = row.get(c).getType();
if (dt != DataType.NULL) {
t = dt.getCoreType();
break;
}
}
types.add(t);
}
}

boolean prependTimestamp =
withImplicitTimestamp && !names.contains(OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP);
RelDataType tsType =
OpenSearchTypeFactory.convertExprTypeToRelDataType(ExprCoreType.TIMESTAMP, false);

var typeBuilder = relBuilder.getTypeFactory().builder();
if (prependTimestamp) {
typeBuilder.add(OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP, tsType);
}
throw new CalciteUnsupportedException("Inline VALUES with literal rows is unsupported");
for (int i = 0; i < nc; i++) {
typeBuilder.add(
names.get(i), OpenSearchTypeFactory.convertExprTypeToRelDataType(types.get(i), true));
}
RelDataType rowType = typeBuilder.build();

if (rows.isEmpty()) {
// header-only CSV / empty JSON array: a zero-row relation with the resolved schema.
relBuilder.values(ImmutableList.<ImmutableList<RexLiteral>>of(), rowType);
return relBuilder.peek();
}

// Build literal rows from the raw Java values, letting RelBuilder infer the initial types, then
// project each column cast to the resolved (OpenSearch-faithful) type.
Object[] flat = new Object[rows.size() * nc];
int k = 0;
for (List<Literal> row : rows) {
for (Literal cell : row) {
flat[k++] = cell.getValue();
}
}
relBuilder.values(names.toArray(new String[0]), flat);
List<RexNode> projects = new java.util.ArrayList<>();
if (prependTimestamp) {
projects.add(
relBuilder.alias(
relBuilder.call(PPLBuiltinOperators.NOW),
OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP));
}
for (int i = 0; i < nc; i++) {
projects.add(
relBuilder.alias(
relBuilder.cast(
relBuilder.field(i),
rowType.getField(names.get(i), true, false).getType().getSqlTypeName()),
names.get(i)));
}
relBuilder.project(projects);
return relBuilder.peek();
}

@Override
public RelNode visitMakeResults(MakeResults node, CalcitePlanContext context) {
// Count path only: the `format=csv|json data=...` form is parsed into a shared Values node
// (see MakeResultsDataParser) and handled by visitValues.
RelBuilder relBuilder = context.relBuilder;
int count = node.getCount();
RelDataType tsType =
OpenSearchTypeFactory.convertExprTypeToRelDataType(ExprCoreType.TIMESTAMP, false);
if (count == 0) {
RelDataType rowType =
relBuilder
.getTypeFactory()
.builder()
.add(OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP, tsType)
.build();
relBuilder.values(ImmutableList.<ImmutableList<RexLiteral>>of(), rowType);
return relBuilder.peek();
}
// Build `count` one-column dummy rows, then project them away with a single @timestamp column
// set to query time. @timestamp is OpenSearch's implicit time field, recognized by the
// time-aware commands (timechart, reverse, span, timewrap). The dummy column only carries row
// multiplicity.
Object[] dummy = new Object[count];
for (int i = 0; i < count; i++) {
dummy[i] = i;
}
relBuilder.values(new String[] {"__makeresults_dummy__"}, dummy);
RexNode now = relBuilder.call(PPLBuiltinOperators.NOW);
relBuilder.project(
List.of(relBuilder.alias(now, OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP)));
return relBuilder.peek();
}

@Override
Expand Down
Loading
Loading