Skip to content

Commit

Permalink
[8.x] ESQL Support IN operator for Date nanos (#119772) (#120126)
Browse files Browse the repository at this point in the history
* ESQL Support IN operator for Date nanos (#119772)

Add support for using nanosecond dates with the IN operator. This behavior should be consistent with equals, and support comparisons between milliseconds and nanoseconds the same as the binary comparison operators support it.

Resolves #118578

---------

Co-authored-by: elasticsearchmachine <[email protected]>

* remove use of future java functions

---------

Co-authored-by: elasticsearchmachine <[email protected]>
  • Loading branch information
not-napoleon and elasticsearchmachine authored Jan 14, 2025
1 parent cf63a73 commit 7603ede
Show file tree
Hide file tree
Showing 11 changed files with 622 additions and 30 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/119772.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 119772
summary: ESQL Support IN operator for Date nanos
area: ES|QL
type: enhancement
issues:
- 118578

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/reference/esql/functions/layout/qstr.asciidoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 21 additions & 6 deletions x-pack/plugin/esql/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,9 @@ tasks.named('checkstyleMain').configure {
exclude { normalize(it.file.toString()).contains("src/main/generated") }
}

def prop(Type, type, TYPE, BYTES, Array) {
def prop(Name, Type, type, TYPE, BYTES, Array) {
return [
"Name" : Name,
"Type" : Type,
"type" : type,
"TYPE" : TYPE,
Expand All @@ -290,15 +291,19 @@ def prop(Type, type, TYPE, BYTES, Array) {
"double" : type == "double" ? "true" : "",
"BytesRef" : type == "BytesRef" ? "true" : "",
"boolean" : type == "boolean" ? "true" : "",
"nanosMillis" : Name == "NanosMillis" ? "true" : "",
"millisNanos" : Name == "MillisNanos" ? "true" : "",
]
}

tasks.named('stringTemplates').configure {
var intProperties = prop("Int", "int", "INT", "Integer.BYTES", "IntArray")
var longProperties = prop("Long", "long", "LONG", "Long.BYTES", "LongArray")
var doubleProperties = prop("Double", "double", "DOUBLE", "Double.BYTES", "DoubleArray")
var bytesRefProperties = prop("BytesRef", "BytesRef", "BYTES_REF", "org.apache.lucene.util.RamUsageEstimator.NUM_BYTES_OBJECT_REF", "")
var booleanProperties = prop("Boolean", "boolean", "BOOLEAN", "Byte.BYTES", "BitArray")
var intProperties = prop("Int", "Int", "int", "INT", "Integer.BYTES", "IntArray")
var longProperties = prop("Long", "Long", "long", "LONG", "Long.BYTES", "LongArray")
var nanosMillisProperties = prop("NanosMillis", "Long", "long", "LONG", "Long.BYTES", "LongArray")
var millisNanosProperties = prop("MillisNanos", "Long", "long", "LONG", "Long.BYTES", "LongArray")
var doubleProperties = prop("Double", "Double", "double", "DOUBLE", "Double.BYTES", "DoubleArray")
var bytesRefProperties = prop("BytesRef", "BytesRef", "BytesRef", "BYTES_REF", "org.apache.lucene.util.RamUsageEstimator.NUM_BYTES_OBJECT_REF", "")
var booleanProperties = prop("Boolean", "Boolean", "boolean", "BOOLEAN", "Byte.BYTES", "BitArray")

File inInputFile = file("src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/X-InEvaluator.java.st")
template {
Expand All @@ -316,6 +321,16 @@ tasks.named('stringTemplates').configure {
it.inputFile = inInputFile
it.outputFile = "org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InLongEvaluator.java"
}
template {
it.properties = nanosMillisProperties
it.inputFile = inInputFile
it.outputFile = "org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InNanosMillisEvaluator.java"
}
template {
it.properties = millisNanosProperties
it.inputFile = inInputFile
it.outputFile = "org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InMillisNanosEvaluator.java"
}
template {
it.properties = doubleProperties
it.inputFile = inInputFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1451,3 +1451,47 @@ FROM employees
cnt:long
19
;

implicit casting strings to dates for IN operator
FROM employees
| WHERE birth_date IN ("1953-04-20", "1958-10-31")
| KEEP emp_no, first_name;
ignoreOrder:true

emp_no:integer | first_name:keyword
10006 | Anneke
10025 | Prasadram
;

IN operator with null in list, finds match

FROM employees
| EVAL x = NULL
| WHERE birth_date IN (TO_DATETIME("1958-02-19T00:00:00Z"), x)
| KEEP birth_date, first_name;

birth_date:datetime | first_name:keyword
1958-02-19T00:00:00.000Z | Saniya
;

IN operator with null in list, doesn't find match

FROM employees
| EVAL x = NULL
| WHERE birth_date IN (TO_DATETIME("1900-02-19T00:00:00Z"), x)
| KEEP birth_date, first_name;

birth_date:datetime | first_name:keyword
;

IN operator with null in list, doesn't find match, EVAL to check value

FROM employees
| EVAL x = NULL
| EVAL result = birth_date IN (TO_DATETIME("1900-02-19T00:00:00Z"), x)
| LIMIT 1
| KEEP result;

result:boolean
null
;
Original file line number Diff line number Diff line change
Expand Up @@ -1153,3 +1153,68 @@ row dt = to_date_nanos(0::long)
plus:date_nanos
1970-01-01T00:00:01.000Z
;

Date Nanos IN constant date nanos
required_capability: date_nanos_in_operator
required_capability: to_date_nanos

FROM date_nanos
| WHERE MV_FIRST(nanos) IN (TO_DATE_NANOS("2023-10-23T13:55:01.543123456Z"), TO_DATE_NANOS("2023-10-23T12:27:28.948Z"), TO_DATE_NANOS("2017-10-23T13:53:55.832987654Z"));
ignoreOrder:true

millis:date | nanos:date_nanos | num:long
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z | 1698069301543123456
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z | 1698064048948000000
;

Date Nanos IN constant date nanos, implicit casting
required_capability: date_nanos_in_operator
required_capability: to_date_nanos
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_FIRST(nanos) IN ("2023-10-23T13:55:01.543123456Z", "2023-10-23T12:27:28.948Z", "2017-10-23T13:53:55.832987654Z");
ignoreOrder:true

millis:date | nanos:date_nanos | num:long
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z | 1698069301543123456
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z | 1698064048948000000
;

Date Nanos IN date nanos field, implicit casting
required_capability: date_nanos_in_operator
required_capability: to_date_nanos
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE "2023-10-23T13:55:01.543123456Z" IN (MV_FIRST(nanos));

millis:date | nanos:date_nanos | num:long
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z | 1698069301543123456
;

Date nanos IN millisecond date field
required_capability: date_nanos_in_operator
required_capability: to_date_nanos
required_capability: date_nanos_implicit_casting

# Note: It is important to have two dates in the IN so the optimizer doesn't turn this into an ==
FROM date_nanos
| WHERE MV_FIRST(nanos) IN (TO_DATETIME("2023-10-23T12:27:28.948"), TO_DATETIME("2020-02-02"));

millis:date | nanos:date_nanos | num:long
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z | 1698064048948000000
;

Millisecond field IN date nanos constants
required_capability: date_nanos_in_operator
required_capability: to_date_nanos
required_capability: date_nanos_implicit_casting

# Note: It is important to have two dates in the IN so the optimizer doesn't turn this into an ==
FROM date_nanos
| WHERE MV_FIRST(millis) IN (TO_DATE_NANOS("2023-10-23T12:27:28.948"), TO_DATE_NANOS("2020-02-02"));

millis:date | nanos:date_nanos | num:long
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z | 1698064048948000000
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.expression.predicate.operator.comparison;

import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BooleanBlock;
import org.elasticsearch.compute.data.LongBlock;
import org.elasticsearch.compute.data.LongVector;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.compute.operator.EvalOperator;
import org.elasticsearch.compute.operator.Warnings;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.core.Releasables;
import org.elasticsearch.xpack.esql.core.tree.Source;

import java.util.Arrays;
import java.util.BitSet;

/**
* {@link EvalOperator.ExpressionEvaluator} implementation for {@link In}.
* This class is generated. Edit {@code X-InEvaluator.java.st} instead.
*/
public class InMillisNanosEvaluator implements EvalOperator.ExpressionEvaluator {
private final Source source;

private final EvalOperator.ExpressionEvaluator lhs;

private final EvalOperator.ExpressionEvaluator[] rhs;

private final DriverContext driverContext;

private Warnings warnings;

public InMillisNanosEvaluator(
Source source,
EvalOperator.ExpressionEvaluator lhs,
EvalOperator.ExpressionEvaluator[] rhs,
DriverContext driverContext
) {
this.source = source;
this.lhs = lhs;
this.rhs = rhs;
this.driverContext = driverContext;
}

@Override
public Block eval(Page page) {
try (LongBlock lhsBlock = (LongBlock) lhs.eval(page)) {
LongBlock[] rhsBlocks = new LongBlock[rhs.length];
try (Releasable rhsRelease = Releasables.wrap(rhsBlocks)) {
for (int i = 0; i < rhsBlocks.length; i++) {
rhsBlocks[i] = (LongBlock) rhs[i].eval(page);
}
LongVector lhsVector = lhsBlock.asVector();
if (lhsVector == null) {
return eval(page.getPositionCount(), lhsBlock, rhsBlocks);
}
LongVector[] rhsVectors = new LongVector[rhs.length];
for (int i = 0; i < rhsBlocks.length; i++) {
rhsVectors[i] = rhsBlocks[i].asVector();
if (rhsVectors[i] == null) {
return eval(page.getPositionCount(), lhsBlock, rhsBlocks);
}
}
return eval(page.getPositionCount(), lhsVector, rhsVectors);
}
}
}

private BooleanBlock eval(int positionCount, LongBlock lhsBlock, LongBlock[] rhsBlocks) {
try (BooleanBlock.Builder result = driverContext.blockFactory().newBooleanBlockBuilder(positionCount)) {
long[] rhsValues = new long[rhs.length];
BitSet nulls = new BitSet(rhs.length);
BitSet mvs = new BitSet(rhs.length);
boolean foundMatch;
for (int p = 0; p < positionCount; p++) {
if (lhsBlock.isNull(p)) {
result.appendNull();
continue;
}
if (lhsBlock.getValueCount(p) != 1) {
if (lhsBlock.getValueCount(p) > 1) {
warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
}
result.appendNull();
continue;
}
// unpack rhsBlocks into rhsValues
nulls.clear();
mvs.clear();
for (int i = 0; i < rhsBlocks.length; i++) {
if (rhsBlocks[i].isNull(p)) {
nulls.set(i);
continue;
}
if (rhsBlocks[i].getValueCount(p) > 1) {
mvs.set(i);
warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
continue;
}
int o = rhsBlocks[i].getFirstValueIndex(p);
rhsValues[i] = rhsBlocks[i].getLong(o);
}
if (nulls.cardinality() == rhsBlocks.length || mvs.cardinality() == rhsBlocks.length) {
result.appendNull();
continue;
}
foundMatch = In.process(nulls, mvs, lhsBlock.getLong(lhsBlock.getFirstValueIndex(p)), rhsValues);
if (foundMatch) {
result.appendBoolean(true);
} else {
if (nulls.cardinality() > 0) {
result.appendNull();
} else {
result.appendBoolean(false);
}
}
}
return result.build();
}
}

private BooleanBlock eval(int positionCount, LongVector lhsVector, LongVector[] rhsVectors) {
try (BooleanBlock.Builder result = driverContext.blockFactory().newBooleanBlockBuilder(positionCount)) {
long[] rhsValues = new long[rhs.length];
for (int p = 0; p < positionCount; p++) {
// unpack rhsVectors into rhsValues
for (int i = 0; i < rhsVectors.length; i++) {
rhsValues[i] = rhsVectors[i].getLong(p);
}
result.appendBoolean(In.processMillisNanos(null, null, lhsVector.getLong(p), rhsValues));
}
return result.build();
}
}

@Override
public String toString() {
return "InMillisNanosEvaluator[" + "lhs=" + lhs + ", rhs=" + Arrays.toString(rhs) + "]";
}

@Override
public void close() {
Releasables.closeExpectNoException(lhs, () -> Releasables.close(rhs));
}

private Warnings warnings() {
if (warnings == null) {
this.warnings = Warnings.createWarnings(
driverContext.warningsMode(),
source.source().getLineNumber(),
source.source().getColumnNumber(),
source.text()
);
}
return warnings;
}

static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
private final Source source;
private final EvalOperator.ExpressionEvaluator.Factory lhs;
private final EvalOperator.ExpressionEvaluator.Factory[] rhs;

Factory(Source source, EvalOperator.ExpressionEvaluator.Factory lhs, EvalOperator.ExpressionEvaluator.Factory[] rhs) {
this.source = source;
this.lhs = lhs;
this.rhs = rhs;
}

@Override
public InMillisNanosEvaluator get(DriverContext context) {
EvalOperator.ExpressionEvaluator[] rhs = Arrays.stream(this.rhs)
.map(a -> a.get(context))
.toArray(EvalOperator.ExpressionEvaluator[]::new);
return new InMillisNanosEvaluator(source, lhs.get(context), rhs, context);
}

@Override
public String toString() {
return "InMillisNanosEvaluator[" + "lhs=" + lhs + ", rhs=" + Arrays.toString(rhs) + "]";
}
}
}
Loading

0 comments on commit 7603ede

Please sign in to comment.