Skip to content
Merged
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 @@ -59,6 +59,7 @@
import org.opensearch.sql.ast.tree.AD;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.AppendCol;
import org.opensearch.sql.ast.tree.Bin;
import org.opensearch.sql.ast.tree.CloseCursor;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.Eval;
Expand Down Expand Up @@ -670,6 +671,12 @@ public LogicalPlan visitML(ML node, AnalysisContext context) {
return new LogicalML(child, node.getArguments());
}

@Override
public LogicalPlan visitBin(Bin node, AnalysisContext context) {
throw new UnsupportedOperationException(
"Bin command is supported only when " + CALCITE_ENGINE_ENABLED.getKeyValue() + "=true");
}

@Override
public LogicalPlan visitExpand(Expand expand, AnalysisContext context) {
throw new UnsupportedOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.opensearch.sql.ast.tree.AD;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.AppendCol;
import org.opensearch.sql.ast.tree.Bin;
import org.opensearch.sql.ast.tree.CloseCursor;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.Eval;
Expand Down Expand Up @@ -213,6 +214,10 @@ public T visitBetween(Between node, C context) {
return visitChildren(node, context);
}

public T visitBin(Bin node, C context) {
return visitChildren(node, context);
}

public T visitArgument(Argument node, C context) {
return visitChildren(node, context);
}
Expand Down
94 changes: 94 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,30 @@
import org.opensearch.sql.ast.expression.WindowFunction;
import org.opensearch.sql.ast.expression.Xor;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.Bin;
import org.opensearch.sql.ast.tree.CountBin;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.DefaultBin;
import org.opensearch.sql.ast.tree.DescribeRelation;
import org.opensearch.sql.ast.tree.Eval;
import org.opensearch.sql.ast.tree.Expand;
import org.opensearch.sql.ast.tree.FillNull;
import org.opensearch.sql.ast.tree.Filter;
import org.opensearch.sql.ast.tree.Head;
import org.opensearch.sql.ast.tree.Limit;
import org.opensearch.sql.ast.tree.MinSpanBin;
import org.opensearch.sql.ast.tree.Parse;
import org.opensearch.sql.ast.tree.Patterns;
import org.opensearch.sql.ast.tree.Project;
import org.opensearch.sql.ast.tree.RangeBin;
import org.opensearch.sql.ast.tree.RareTopN;
import org.opensearch.sql.ast.tree.RareTopN.CommandType;
import org.opensearch.sql.ast.tree.Relation;
import org.opensearch.sql.ast.tree.RelationSubquery;
import org.opensearch.sql.ast.tree.Rename;
import org.opensearch.sql.ast.tree.Sort;
import org.opensearch.sql.ast.tree.Sort.SortOption;
import org.opensearch.sql.ast.tree.SpanBin;
import org.opensearch.sql.ast.tree.SubqueryAlias;
import org.opensearch.sql.ast.tree.TableFunction;
import org.opensearch.sql.ast.tree.Trendline;
Expand Down Expand Up @@ -566,4 +572,92 @@ public static FillNull fillNull(
}
return FillNull.ofVariousValue(replacementsBuilder.build()).attach(input);
}

/**
* Creates a Bin node with an input plan for binning field values into discrete buckets.
*
* @param input the input plan
* @param field the field expression to bin
* @param arguments optional arguments for bin configuration (span, bins, minspan, aligntime,
* start, end, alias)
* @return Bin node attached to the input plan
*/
public static Bin bin(UnresolvedPlan input, UnresolvedExpression field, Argument... arguments) {
Bin binNode = bin(field, arguments);
binNode.attach(input);
return binNode;
}

/**
* Creates a Bin node for binning field values into discrete buckets. Returns the appropriate Bin
* subclass based on parameter priority: 1. SPAN (highest) -> SpanBin 2. MINSPAN -> MinSpanBin 3.
* BINS -> CountBin 4. START/END only -> RangeBin 5. No params -> DefaultBin
*
* @param field the field expression to bin
* @param arguments optional arguments for bin configuration (span, bins, minspan, aligntime,
* start, end, alias)
* @return Bin node with the specified field and configuration
*/
public static Bin bin(UnresolvedExpression field, Argument... arguments) {
UnresolvedExpression span = null;
Integer bins = null;
UnresolvedExpression minspan = null;
UnresolvedExpression aligntime = null;
UnresolvedExpression start = null;
UnresolvedExpression end = null;
String alias = null;

for (Argument arg : arguments) {
switch (arg.getArgName()) {
case "span":
span = arg.getValue();
break;
case "bins":
bins =
(arg.getValue()).getValue() instanceof Integer
? (Integer) (arg.getValue()).getValue()
: null;
break;
case "minspan":
minspan = arg.getValue();
break;
case "aligntime":
aligntime = arg.getValue();
break;
case "start":
start = arg.getValue();
break;
case "end":
end = arg.getValue();
break;
case "alias":
alias = arg.getValue().toString();
break;
}
}

// Create appropriate Bin subclass based on priority order
if (span != null) {
// 1. SPAN (highest priority) -> SpanBin
return SpanBin.builder().field(field).span(span).aligntime(aligntime).alias(alias).build();
} else if (minspan != null) {
// 2. MINSPAN (second priority) -> MinSpanBin
return MinSpanBin.builder()
.field(field)
.minspan(minspan)
.start(start)
.end(end)
.alias(alias)
.build();
} else if (bins != null) {
// 3. BINS (third priority) -> CountBin
return CountBin.builder().field(field).bins(bins).start(start).end(end).alias(alias).build();
} else if (start != null || end != null) {
// 4. START/END only (fourth priority) -> RangeBin
return RangeBin.builder().field(field).start(start).end(end).alias(alias).build();
} else {
// 5. No parameters (default) -> DefaultBin
return DefaultBin.builder().field(field).alias(alias).build();
}
}
}
57 changes: 57 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/Bin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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 javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.opensearch.sql.ast.AbstractNodeVisitor;
import org.opensearch.sql.ast.expression.UnresolvedExpression;

/** Abstract AST node representing Bin operations with type-safe derived classes. */
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = false)
public abstract class Bin extends UnresolvedPlan {

private UnresolvedPlan child;

protected final UnresolvedExpression field;

@Nullable protected final String alias;

protected Bin(UnresolvedExpression field, @Nullable String alias) {
this.field = field;
this.alias = alias;
}

/**
* Validates the parameters specific to this bin type. Each subclass implements its own validation
* logic.
*/
public abstract void validate();

@Override
public Bin attach(UnresolvedPlan child) {
this.child = child;
return this;
}

@Override
public List<UnresolvedPlan> getChild() {
return this.child == null ? ImmutableList.of() : ImmutableList.of(this.child);
}

@Override
public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) {
return nodeVisitor.visitBin(this, context);
}
}
55 changes: 55 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/CountBin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.ast.tree;

import javax.annotation.Nullable;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.opensearch.sql.ast.expression.UnresolvedExpression;
import org.opensearch.sql.calcite.utils.binning.BinConstants;

/**
* AST node representing count-based bin operation. This is the third priority bin type that uses
* "nice number" algorithm to create a specific number of bins. Supports start/end range parameters.
*/
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class CountBin extends Bin {

private final Integer bins;

@Nullable private final UnresolvedExpression start;

@Nullable private final UnresolvedExpression end;

@Builder
public CountBin(
UnresolvedExpression field,
@Nullable String alias,
Integer bins,
@Nullable UnresolvedExpression start,
@Nullable UnresolvedExpression end) {
super(field, alias);
this.bins = bins;
this.start = start;
this.end = end;
validate();
}

@Override
public void validate() {
// Bins count validation based on documentation
if (bins < BinConstants.MIN_BINS || bins > BinConstants.MAX_BINS) {
throw new IllegalArgumentException(
String.format(
"The bins parameter must be between %d and %d, got: %d",
BinConstants.MIN_BINS, BinConstants.MAX_BINS, bins));
}
}
}
35 changes: 35 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/DefaultBin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.ast.tree;

import javax.annotation.Nullable;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.opensearch.sql.ast.expression.UnresolvedExpression;

/**
* AST node representing default magnitude-based bin operation. This is the lowest priority bin type
* that uses automatic magnitude-based algorithm when no explicit binning parameters are specified.
*/
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DefaultBin extends Bin {

@Builder
public DefaultBin(UnresolvedExpression field, @Nullable String alias) {
super(field, alias);
validate();
}

@Override
public void validate() {
// Default bin has no additional parameters to validate
// Field validation is already handled in the base class
}
}
46 changes: 46 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/MinSpanBin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.ast.tree;

import javax.annotation.Nullable;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.opensearch.sql.ast.expression.UnresolvedExpression;

/**
* AST node representing minimum span-based bin operation. This is the second priority bin type that
* uses magnitude-based algorithm with minimum span constraint. Supports start/end range parameters.
*/
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class MinSpanBin extends Bin {

private final UnresolvedExpression minspan;

@Nullable private final UnresolvedExpression start;

@Nullable private final UnresolvedExpression end;

@Builder
public MinSpanBin(
UnresolvedExpression field,
@Nullable String alias,
UnresolvedExpression minspan,
@Nullable UnresolvedExpression start,
@Nullable UnresolvedExpression end) {
super(field, alias);
this.minspan = minspan;
this.start = start;
this.end = end;
validate();
}

@Override
public void validate() {}
}
Loading
Loading