Skip to content

Commit

Permalink
Add addfield command
Browse files Browse the repository at this point in the history
  • Loading branch information
normanj-bitquill committed Oct 10, 2024
1 parent ac8678c commit 3d9aaa7
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 0 deletions.
18 changes: 18 additions & 0 deletions core/src/main/java/org/opensearch/sql/analysis/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.opensearch.sql.ast.expression.QualifiedName;
import org.opensearch.sql.ast.expression.UnresolvedExpression;
import org.opensearch.sql.ast.tree.AD;
import org.opensearch.sql.ast.tree.AddField;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.CloseCursor;
import org.opensearch.sql.ast.tree.Dedupe;
Expand All @@ -65,6 +66,7 @@
import org.opensearch.sql.ast.tree.Values;
import org.opensearch.sql.common.antlr.SyntaxCheckException;
import org.opensearch.sql.data.model.ExprMissingValue;
import org.opensearch.sql.data.model.ExprValueUtils;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.datasource.DataSourceService;
import org.opensearch.sql.exception.SemanticCheckException;
Expand Down Expand Up @@ -464,6 +466,22 @@ public LogicalPlan visitParse(Parse node, AnalysisContext context) {
return child;
}

@Override
public LogicalPlan visitAddField(final AddField node, final AnalysisContext context) {
LogicalPlan child = node.getChild().get(0).accept(this, context);
ImmutableList.Builder<Pair<ReferenceExpression, Expression>> expressionsBuilder =
new Builder<>();
Expression valueExpression =
new LiteralExpression(ExprValueUtils.stringValue((String) node.getFieldValue().getValue()));
ReferenceExpression ref =
DSL.ref((String) node.getFieldName().getValue(), valueExpression.type());
expressionsBuilder.add(ImmutablePair.of(ref, valueExpression));
TypeEnvironment typeEnvironment = context.peek();
// define the new reference in type env.
typeEnvironment.define(ref);
return new LogicalEval(child, expressionsBuilder.build());
}

/** Build {@link LogicalSort}. */
@Override
public LogicalPlan visitSort(Sort node, AnalysisContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.opensearch.sql.ast.statement.Query;
import org.opensearch.sql.ast.statement.Statement;
import org.opensearch.sql.ast.tree.AD;
import org.opensearch.sql.ast.tree.AddField;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.CloseCursor;
import org.opensearch.sql.ast.tree.Dedupe;
Expand Down Expand Up @@ -201,6 +202,10 @@ public T visitEval(Eval node, C context) {
return visitChildren(node, context);
}

public T visitAddField(AddField node, C context) {
return visitChildren(node, context);
}

public T visitParse(Parse node, C context) {
return visitChildren(node, context);
}
Expand Down
5 changes: 5 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 @@ -43,6 +43,7 @@
import org.opensearch.sql.ast.expression.When;
import org.opensearch.sql.ast.expression.WindowFunction;
import org.opensearch.sql.ast.expression.Xor;
import org.opensearch.sql.ast.tree.AddField;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.Eval;
Expand Down Expand Up @@ -471,4 +472,8 @@ public static Parse parse(
java.util.Map<String, Literal> arguments) {
return new Parse(parseMethod, sourceField, pattern, arguments, input);
}

public static AddField addField(UnresolvedPlan input, Literal fieldName, Literal fieldValue) {
return new AddField(input, fieldName, fieldValue);
}
}
48 changes: 48 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/AddField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.opensearch.sql.ast.AbstractNodeVisitor;
import org.opensearch.sql.ast.expression.Literal;

/** AST node represent AddField operation. */
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
@AllArgsConstructor
public class AddField extends UnresolvedPlan {
/** Child Plan. */
private UnresolvedPlan child;

private final Literal fieldName;
private final Literal fieldValue;

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

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

@Override
public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) {
return nodeVisitor.visitAddField(this, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.planner.logical;

import static org.opensearch.sql.data.type.ExprCoreType.STRING;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opensearch.sql.analysis.AnalyzerTestBase;
import org.opensearch.sql.ast.dsl.AstDSL;
import org.opensearch.sql.expression.DSL;

@ExtendWith(MockitoExtension.class)
public class LogicalAddFieldTest extends AnalyzerTestBase {
@Test
public void analyze_addfield_with_one_field() {
assertAnalyzeEqual(
LogicalPlanDSL.eval(
LogicalPlanDSL.relation("schema", table),
ImmutablePair.of(DSL.ref("x", STRING), DSL.literal("foo"))),
AstDSL.addField(
AstDSL.relation("schema"), AstDSL.stringLiteral("x"), AstDSL.stringLiteral("foo")));
}
}
44 changes: 44 additions & 0 deletions docs/user/ppl/cmd/addfield.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=============
addfield
=============

.. rubric:: Table of contents

.. contents::
:local:
:depth: 2


Description
============
| The ``addfield`` command adds a field with a constant value.

Syntax
============
addfield <field_name> <field_value>

* <field_name>: mandatory string. name of field to add
* <field_value>: mandatory string. value of new field

Example 1: Add a field named "foo" with the value "bar"
===========================================

The example show maximum 10 results from accounts index.

PPL query::

os> source=accounts | fields firstname, age | addfield 'foo' 'bar';
fetched rows / total rows = 4/4
+-------------+-------+-------+
| firstname | age | foo |
|-------------+-------+-------|
| Amber | 32 | bar |
| Hattie | 36 | bar |
| Nanette | 28 | bar |
| Dale | 33 | bar |
+-------------+-------+-------+

Limitation
==========
The ``addfield`` command is not rewritten to OpenSearch DSL, it is only executed on the coordination node.
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.ppl;

import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT;
import static org.opensearch.sql.util.MatcherUtils.rows;
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;

import java.io.IOException;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.Test;

public class AddFieldCommandIT extends PPLIntegTestCase {
@Before
public void beforeTest() throws IOException {
setQuerySizeLimit(200);
}

@After
public void afterTest() throws IOException {
resetQuerySizeLimit();
resetMaxResultWindow(TEST_INDEX_ACCOUNT);
}

@Override
public void init() throws IOException {
loadIndex(Index.ACCOUNT);
}

@Test
public void testAddField() throws IOException {
JSONObject result =
executeQuery(
String.format(
"source=%s | fields firstname, age | head | addfield 'x' 'foo'",
TEST_INDEX_ACCOUNT));
verifyDataRows(
result,
rows("Amber", 32, "foo"),
rows("Hattie", 36, "foo"),
rows("Nanette", 28, "foo"),
rows("Dale", 33, "foo"),
rows("Elinor", 36, "foo"),
rows("Virginia", 39, "foo"),
rows("Dillard", 34, "foo"),
rows("Mcgee", 39, "foo"),
rows("Aurelia", 37, "foo"),
rows("Fulton", 23, "foo"));
}
}
1 change: 1 addition & 0 deletions ppl/src/main/antlr/OpenSearchPPLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ NEW_FIELD: 'NEW_FIELD';
KMEANS: 'KMEANS';
AD: 'AD';
ML: 'ML';
ADDFIELD: 'ADDFIELD';

// COMMAND ASSIST KEYWORDS
AS: 'AS';
Expand Down
5 changes: 5 additions & 0 deletions ppl/src/main/antlr/OpenSearchPPLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ commands
| kmeansCommand
| adCommand
| mlCommand
| addfieldCommand
;

searchCommand
Expand Down Expand Up @@ -113,6 +114,10 @@ parseCommand
: PARSE (source_field = expression) (pattern = stringLiteral)
;

addfieldCommand
: ADDFIELD (field_name = stringLiteral) (field_value = stringLiteral)
;

patternsCommand
: PATTERNS (patternsParameter)* (source_field = expression)
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.opensearch.sql.ast.expression.UnresolvedArgument;
import org.opensearch.sql.ast.expression.UnresolvedExpression;
import org.opensearch.sql.ast.tree.AD;
import org.opensearch.sql.ast.tree.AddField;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.Eval;
Expand Down Expand Up @@ -270,6 +271,14 @@ public UnresolvedPlan visitGrokCommand(OpenSearchPPLParser.GrokCommandContext ct
return new Parse(ParseMethod.GROK, sourceField, pattern, ImmutableMap.of());
}

@Override
public UnresolvedPlan visitAddfieldCommand(final OpenSearchPPLParser.AddfieldCommandContext ctx) {
Literal fieldName = (Literal) internalVisitExpression(ctx.field_name);
Literal fieldValue = (Literal) internalVisitExpression(ctx.field_value);

return new AddField(fieldName, fieldValue);
}

@Override
public UnresolvedPlan visitParseCommand(OpenSearchPPLParser.ParseCommandContext ctx) {
UnresolvedExpression sourceField = internalVisitExpression(ctx.source_field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.opensearch.sql.ast.statement.Explain;
import org.opensearch.sql.ast.statement.Query;
import org.opensearch.sql.ast.statement.Statement;
import org.opensearch.sql.ast.tree.AddField;
import org.opensearch.sql.ast.tree.Aggregation;
import org.opensearch.sql.ast.tree.Dedupe;
import org.opensearch.sql.ast.tree.Eval;
Expand Down Expand Up @@ -189,6 +190,13 @@ public String visitEval(Eval node, String context) {
return StringUtils.format("%s | eval %s", child, expressions);
}

@Override
public String visitAddField(final AddField node, final String context) {
String child = node.getChild().get(0).accept(this, context);
return StringUtils.format(
"%s | addfield %s %s", child, node.getFieldName(), node.getFieldValue());
}

/** Build {@link LogicalSort}. */
@Override
public String visitSort(Sort node, String context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
import static org.opensearch.sql.ast.dsl.AstDSL.addField;
import static org.opensearch.sql.ast.dsl.AstDSL.agg;
import static org.opensearch.sql.ast.dsl.AstDSL.aggregate;
import static org.opensearch.sql.ast.dsl.AstDSL.alias;
Expand Down Expand Up @@ -437,6 +438,13 @@ public void testEvalCommand() {
eval(relation("t"), let(field("r"), function("abs", field("f")))));
}

@Test
public void testAddfieldCommand() {
assertEqual(
"source=t | addfield 'x' 'foo'",
addField(relation("t"), stringLiteral("x"), stringLiteral("foo")));
}

@Test
public void testIndexName() {
assertEqual(
Expand Down

0 comments on commit 3d9aaa7

Please sign in to comment.