Skip to content

Commit ed2d080

Browse files
committed
Merge remote-tracking branch 'origin/main' into pushdown-limit
2 parents d5a9495 + 93fa20f commit ed2d080

84 files changed

Lines changed: 20693 additions & 254 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/src/main/java/org/opensearch/sql/ast/statement/Explain.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.opensearch.sql.ast.statement;
1010

11+
import java.util.Locale;
1112
import lombok.EqualsAndHashCode;
1213
import lombok.Getter;
1314
import org.opensearch.sql.ast.AbstractNodeVisitor;
@@ -46,7 +47,7 @@ public enum ExplainFormat {
4647

4748
public static ExplainFormat format(String format) {
4849
try {
49-
return ExplainFormat.valueOf(format.toUpperCase());
50+
return ExplainFormat.valueOf(format.toUpperCase(Locale.ROOT));
5051
} catch (Exception e) {
5152
return ExplainFormat.STANDARD;
5253
}

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.calcite.tools.RelBuilder;
3838
import org.apache.calcite.tools.RelBuilder.AggCall;
3939
import org.apache.calcite.util.Holder;
40+
import org.apache.calcite.util.Pair;
4041
import org.checkerframework.checker.nullness.qual.Nullable;
4142
import org.opensearch.sql.ast.AbstractNodeVisitor;
4243
import org.opensearch.sql.ast.Node;
@@ -78,6 +79,7 @@
7879
import org.opensearch.sql.ast.tree.Window;
7980
import org.opensearch.sql.calcite.plan.OpenSearchConstants;
8081
import org.opensearch.sql.calcite.utils.JoinAndLookupUtils;
82+
import org.opensearch.sql.calcite.utils.PlanUtils;
8183
import org.opensearch.sql.exception.CalciteUnsupportedException;
8284
import org.opensearch.sql.exception.SemanticCheckException;
8385
import org.opensearch.sql.expression.function.PPLFuncImpTable;
@@ -371,10 +373,9 @@ private void projectPlusOverriding(
371373
context.relBuilder.rename(expectedRenameFields);
372374
}
373375

374-
@Override
375-
public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) {
376-
visitChildren(node, context);
377-
List<AggCall> aggList =
376+
private Pair<List<AggCall>, List<RexNode>> resolveAggCallAndGroupBy(
377+
Aggregation node, CalcitePlanContext context) {
378+
List<AggCall> aggCallList =
378379
node.getAggExprList().stream()
379380
.map(expr -> aggVisitor.analyze(expr, context))
380381
.collect(Collectors.toList());
@@ -389,7 +390,37 @@ public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) {
389390
}
390391
groupByList.addAll(
391392
node.getGroupExprList().stream().map(expr -> rexVisitor.analyze(expr, context)).toList());
393+
return Pair.of(aggCallList, groupByList);
394+
}
392395

396+
@Override
397+
public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) {
398+
visitChildren(node, context);
399+
// Add a trimmed Project before Aggregate.
400+
// to avoid bugs in RelDecorrelator.decorrelateRel(Aggregate rel)
401+
// For example:
402+
// source=t | where a > 1 | stats avg(b+1) by c
403+
// Before:
404+
// Aggregate
405+
// \- Filter(a>1)
406+
// \- Scan t
407+
// After:
408+
// Aggregate
409+
// \- Project([c,b])
410+
// \- Filter(a>1)
411+
// \- Scan t
412+
Pair<List<AggCall>, List<RexNode>> resolved = resolveAggCallAndGroupBy(node, context);
413+
List<RexInputRef> trimmedRefs = new ArrayList<>();
414+
trimmedRefs.addAll(PlanUtils.getInputRefs(resolved.right)); // group-by keys first
415+
trimmedRefs.addAll(PlanUtils.getInputRefsFromAggCall(resolved.left));
416+
context.relBuilder.project(trimmedRefs);
417+
418+
// Re-resolve aggCalls and group-by list based on adding trimmed Project.
419+
// Using re-resolving rather than Calcite Mapping (ref Calcite ProjectTableScanRule)
420+
// because that Mapping only works for RexNode, but we need both AggCall and RexNode list.
421+
Pair<List<AggCall>, List<RexNode>> reResolved = resolveAggCallAndGroupBy(node, context);
422+
List<AggCall> aggList = reResolved.left;
423+
List<RexNode> groupByList = reResolved.right;
393424
context.relBuilder.aggregate(context.relBuilder.groupKey(groupByList), aggList);
394425

395426
// schema reordering
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.calcite.plan;
7+
8+
import org.apache.calcite.rel.type.RelDataType;
9+
import org.apache.calcite.rel.type.RelDataTypeFactory;
10+
import org.apache.calcite.schema.TranslatableTable;
11+
import org.apache.calcite.schema.impl.AbstractTable;
12+
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
13+
14+
/**
15+
* Abstract class to map the {@link org.opensearch.sql.storage.Table} and {@link
16+
* org.apache.calcite.schema.Table}
17+
*/
18+
public abstract class AbstractOpenSearchTable extends AbstractTable
19+
implements TranslatableTable, org.opensearch.sql.storage.Table {
20+
21+
@Override
22+
public RelDataType getRowType(RelDataTypeFactory relDataTypeFactory) {
23+
return OpenSearchTypeFactory.convertSchema(this);
24+
}
25+
}

core/src/main/java/org/opensearch/sql/calcite/plan/OpenSearchQueryable.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

core/src/main/java/org/opensearch/sql/calcite/plan/OpenSearchTable.java

Lines changed: 0 additions & 50 deletions
This file was deleted.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.lang.reflect.Type;
3434
import java.nio.charset.Charset;
3535
import java.util.ArrayList;
36+
import java.util.LinkedHashMap;
3637
import java.util.List;
3738
import java.util.Locale;
3839
import java.util.Map;
@@ -308,7 +309,7 @@ public static ExprValue getExprValueByExprType(ExprType type, Object value) {
308309
public static RelDataType convertSchema(Table table) {
309310
List<String> fieldNameList = new ArrayList<>();
310311
List<RelDataType> typeList = new ArrayList<>();
311-
Map<String, ExprType> fieldTypes = table.getFieldTypes();
312+
Map<String, ExprType> fieldTypes = new LinkedHashMap<>(table.getFieldTypes());
312313
fieldTypes.putAll(table.getReservedFieldTypes());
313314
for (Entry<String, ExprType> entry : fieldTypes.entrySet()) {
314315
fieldNameList.add(entry.getKey());

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.util.ArrayList;
2121
import java.util.List;
2222
import javax.annotation.Nullable;
23+
import org.apache.calcite.rex.RexInputRef;
2324
import org.apache.calcite.rex.RexNode;
25+
import org.apache.calcite.rex.RexVisitorImpl;
2426
import org.apache.calcite.rex.RexWindowBound;
2527
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
2628
import org.apache.calcite.sql.type.ReturnTypes;
@@ -259,4 +261,34 @@ static RelBuilder.AggCall makeAggCall(
259261
"Unexpected aggregation: " + functionName.getName().getFunctionName());
260262
}
261263
}
264+
265+
/** Get all uniq input references from a RexNode. */
266+
static List<RexInputRef> getInputRefs(RexNode node) {
267+
List<RexInputRef> inputRefs = new ArrayList<>();
268+
node.accept(
269+
new RexVisitorImpl<Void>(true) {
270+
@Override
271+
public Void visitInputRef(RexInputRef inputRef) {
272+
if (!inputRefs.contains(inputRef)) {
273+
inputRefs.add(inputRef);
274+
}
275+
return null;
276+
}
277+
});
278+
return inputRefs;
279+
}
280+
281+
/** Get all uniq input references from a list of RexNodes. */
282+
static List<RexInputRef> getInputRefs(List<RexNode> nodes) {
283+
return nodes.stream().flatMap(node -> getInputRefs(node).stream()).toList();
284+
}
285+
286+
/** Get all uniq input references from a list of agg calls. */
287+
static List<RexInputRef> getInputRefsFromAggCall(List<RelBuilder.AggCall> aggCalls) {
288+
return aggCalls.stream()
289+
.map(RelBuilder.AggCall::over)
290+
.map(RelBuilder.OverCall::toRex)
291+
.flatMap(rex -> getInputRefs(rex).stream())
292+
.toList();
293+
}
262294
}

core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ public enum BuiltinFunctionName {
227227
NULLIF(FunctionName.of("nullif")),
228228
ISNULL(FunctionName.of("isnull")),
229229

230+
IS_PRESENT(FunctionName.of("ispresent")),
231+
IS_EMPTY(FunctionName.of("isempty")),
232+
IS_BLANK(FunctionName.of("isblank")),
233+
230234
ROW_NUMBER(FunctionName.of("row_number")),
231235
RANK(FunctionName.of("rank")),
232236
DENSE_RANK(FunctionName.of("dense_rank")),

core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ void populate() {
202202
registerOperator(IS_NULL, SqlStdOperatorTable.IS_NULL);
203203
registerOperator(IF, SqlStdOperatorTable.CASE);
204204
registerOperator(IFNULL, SqlStdOperatorTable.COALESCE);
205+
registerOperator(IS_PRESENT, SqlStdOperatorTable.IS_NOT_NULL);
205206

206207
// Register library operator
207208
registerOperator(REGEXP, SqlLibraryOperators.REGEXP);
@@ -371,6 +372,28 @@ void populate() {
371372
builder.makeCall(SqlStdOperatorTable.EQUALS, arg1, arg2),
372373
builder.makeNullLiteral(arg1.getType()),
373374
arg1));
375+
register(
376+
IS_EMPTY,
377+
((FunctionImp1)
378+
(builder, arg) ->
379+
builder.makeCall(
380+
SqlStdOperatorTable.OR,
381+
builder.makeCall(SqlStdOperatorTable.IS_NULL, arg),
382+
builder.makeCall(SqlStdOperatorTable.IS_EMPTY, arg))));
383+
register(
384+
IS_BLANK,
385+
((FunctionImp1)
386+
(builder, arg) ->
387+
builder.makeCall(
388+
SqlStdOperatorTable.OR,
389+
builder.makeCall(SqlStdOperatorTable.IS_NULL, arg),
390+
builder.makeCall(
391+
SqlStdOperatorTable.IS_EMPTY,
392+
builder.makeCall(
393+
SqlStdOperatorTable.TRIM,
394+
builder.makeFlag(Flag.BOTH),
395+
builder.makeLiteral(" "),
396+
arg)))));
374397
}
375398
}
376399

docs/user/ppl/functions/condition.rst

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Argument type: all the supported data type.
4545

4646
Return type: BOOLEAN
4747

48+
Synonyms: `ISPRESENT`_
49+
4850
Example::
4951

5052
os> source=accounts | where not isnotnull(employer) | fields account_number, employer
@@ -238,3 +240,85 @@ Example::
238240
| Dale | Adams | 33 |
239241
+-----------+----------+-----+
240242

243+
ISPRESENT
244+
---------
245+
246+
Description
247+
>>>>>>>>>>>
248+
249+
Version: 3.1.0
250+
251+
Usage: ispresent(field) return true if the field exists.
252+
253+
Argument type: all the supported data type.
254+
255+
Return type: BOOLEAN
256+
257+
Synonyms: `ISNOTNULL`_
258+
259+
Example::
260+
261+
PPL> source=accounts | where ispresent(employer) | fields employer, firstname
262+
fetched rows / total rows = 3/3
263+
+----------+-----------+
264+
| employer | firstname |
265+
|----------+-----------|
266+
| Pyrami | Amber |
267+
| Netagy | Hattie |
268+
| Quility | Nanette |
269+
+----------+-----------+
270+
271+
ISBLANK
272+
-------
273+
274+
Description
275+
>>>>>>>>>>>
276+
277+
Version: 3.1.0
278+
279+
Usage: isblank(field) returns true if the field is null, an empty string, or contains only white space.
280+
281+
Argument type: all the supported data type.
282+
283+
Return type: BOOLEAN
284+
285+
Example::
286+
287+
PPL> source=accounts | eval temp = ifnull(employer, ' ') | eval `isblank(employer)` = isblank(employer), `isblank(temp)` = isblank(temp) | fields `isblank(temp)`, temp, `isblank(employer)`, employer
288+
fetched rows / total rows = 4/4
289+
+---------------+---------+-------------------+----------+
290+
| isblank(temp) | temp | isblank(employer) | employer |
291+
|---------------+---------+-------------------+----------|
292+
| False | Pyrami | False | Pyrami |
293+
| False | Netagy | False | Netagy |
294+
| False | Quility | False | Quility |
295+
| True | | True | null |
296+
+---------------+---------+-------------------+----------+
297+
298+
299+
ISEMPTY
300+
-------
301+
302+
Description
303+
>>>>>>>>>>>
304+
305+
Version: 3.1.0
306+
307+
Usage: isempty(field) returns true if the field is null or is an empty string.
308+
309+
Argument type: all the supported data type.
310+
311+
Return type: BOOLEAN
312+
313+
Example::
314+
315+
PPL> source=accounts | eval temp = ifnull(employer, ' ') | eval `isempty(employer)` = isempty(employer), `isempty(temp)` = isempty(temp) | fields `isempty(temp)`, temp, `isempty(employer)`, employer
316+
fetched rows / total rows = 4/4
317+
+---------------+---------+-------------------+----------+
318+
| isempty(temp) | temp | isempty(employer) | employer |
319+
|---------------+---------+-------------------+----------|
320+
| False | Pyrami | False | Pyrami |
321+
| False | Netagy | False | Netagy |
322+
| False | Quility | False | Quility |
323+
| False | | True | null |
324+
+---------------+---------+-------------------+----------+

0 commit comments

Comments
 (0)