diff --git a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Methods.java b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Methods.java index b94eb13433a15..c2d076eee611a 100644 --- a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Methods.java +++ b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Methods.java @@ -38,6 +38,9 @@ import static org.elasticsearch.compute.gen.Types.DOUBLE_VECTOR; import static org.elasticsearch.compute.gen.Types.DOUBLE_VECTOR_BUILDER; import static org.elasticsearch.compute.gen.Types.DOUBLE_VECTOR_FIXED_BUILDER; +import static org.elasticsearch.compute.gen.Types.FLOAT_BLOCK_BUILDER; +import static org.elasticsearch.compute.gen.Types.FLOAT_VECTOR_BUILDER; +import static org.elasticsearch.compute.gen.Types.FLOAT_VECTOR_FIXED_BUILDER; import static org.elasticsearch.compute.gen.Types.INT_BLOCK; import static org.elasticsearch.compute.gen.Types.INT_BLOCK_BUILDER; import static org.elasticsearch.compute.gen.Types.INT_VECTOR; @@ -216,6 +219,9 @@ static String appendMethod(TypeName t) { if (t.equals(TypeName.DOUBLE) || t.equals(DOUBLE_BLOCK) || t.equals(DOUBLE_VECTOR)) { return "appendDouble"; } + if (t.equals(TypeName.FLOAT) || t.equals(FLOAT_BLOCK_BUILDER)) { + return "appendFloat"; + } throw new IllegalArgumentException("unknown append method for [" + t + "]"); } @@ -266,6 +272,15 @@ static String buildFromFactory(TypeName t) { if (t.equals(DOUBLE_VECTOR_FIXED_BUILDER)) { return "newDoubleVectorFixedBuilder"; } + if (t.equals(FLOAT_BLOCK_BUILDER)) { + return "newFloatBlockBuilder"; + } + if (t.equals(FLOAT_VECTOR_BUILDER)) { + return "newFloatVectorBuilder"; + } + if (t.equals(FLOAT_VECTOR_FIXED_BUILDER)) { + return "newFloatVectorFixedBuilder"; + } throw new IllegalArgumentException("unknown build method for [" + t + "]"); } @@ -289,6 +304,9 @@ static String getMethod(TypeName elementType) { if (elementType.equals(TypeName.DOUBLE)) { return "getDouble"; } + if (elementType.equals(TypeName.FLOAT)) { + return "getFloat"; + } throw new IllegalArgumentException("unknown get method for [" + elementType + "]"); } diff --git a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Types.java b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Types.java index a9f4eef521716..729e435934b89 100644 --- a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Types.java +++ b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Types.java @@ -252,6 +252,9 @@ static TypeName elementType(TypeName t) { if (t.equals(DOUBLE_BLOCK) || t.equals(DOUBLE_VECTOR) || t.equals(DOUBLE_BLOCK_BUILDER)) { return TypeName.DOUBLE; } + if (t.equals(FLOAT_BLOCK) || t.equals(FLOAT_VECTOR) || t.equals(FLOAT_BLOCK_BUILDER)) { + return TypeName.FLOAT; + } throw new IllegalArgumentException("unknown element type for [" + t + "]"); } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec index 4932ac553f24c..d5bd9536cb5fe 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec @@ -1842,3 +1842,28 @@ emp_no:integer | l1:double | l2:double 10002 | -7.23 | null 10003 | 4.0 | null ; + +copySignWithMixedNumericValues +required_capability: copy_sign + +ROW cs1=COPY_SIGN(10.0, -5.0), cs2=COPY_SIGN(10, 1), cs3=COPY_SIGN(1, -5.0); + +cs1:double | cs2:integer | cs3:integer +-10.0 | 10 | -1 +; + +copySignWithMixedNumericValuesWithMV +required_capability: copy_sign + +FROM employees +| EVAL cs1 = COPY_SIGN(salary, LEAST(salary_change)) +| KEEP emp_no, salary, salary_change, cs1 +| SORT emp_no +| LIMIT 3 +; + + emp_no:integer | salary:integer | salary_change:double | cs1:integer + 10001 | 57305 | 1.19 | 57305 + 10002 | 56371 | [-7.23, 11.17] | -56371 + 10003 | 61805 | [12.82, 14.68] | 61805 +; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignDoubleEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignDoubleEvaluator.java new file mode 100644 index 0000000000000..4685359679c2f --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignDoubleEvaluator.java @@ -0,0 +1,147 @@ +// 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.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +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.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link CopySign}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class CopySignDoubleEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator magnitude; + + private final EvalOperator.ExpressionEvaluator sign; + + private final DriverContext driverContext; + + private Warnings warnings; + + public CopySignDoubleEvaluator(Source source, EvalOperator.ExpressionEvaluator magnitude, + EvalOperator.ExpressionEvaluator sign, DriverContext driverContext) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock magnitudeBlock = (DoubleBlock) magnitude.eval(page)) { + try (DoubleBlock signBlock = (DoubleBlock) sign.eval(page)) { + DoubleVector magnitudeVector = magnitudeBlock.asVector(); + if (magnitudeVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + DoubleVector signVector = signBlock.asVector(); + if (signVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + return eval(page.getPositionCount(), magnitudeVector, signVector).asBlock(); + } + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock magnitudeBlock, DoubleBlock signBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (magnitudeBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (magnitudeBlock.getValueCount(p) != 1) { + if (magnitudeBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (signBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (signBlock.getValueCount(p) != 1) { + if (signBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(CopySign.processDouble(magnitudeBlock.getDouble(magnitudeBlock.getFirstValueIndex(p)), signBlock.getDouble(signBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, DoubleVector magnitudeVector, + DoubleVector signVector) { + try(DoubleVector.FixedBuilder result = driverContext.blockFactory().newDoubleVectorFixedBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(p, CopySign.processDouble(magnitudeVector.getDouble(p), signVector.getDouble(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "CopySignDoubleEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(magnitude, sign); + } + + 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 magnitude; + + private final EvalOperator.ExpressionEvaluator.Factory sign; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory magnitude, + EvalOperator.ExpressionEvaluator.Factory sign) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + } + + @Override + public CopySignDoubleEvaluator get(DriverContext context) { + return new CopySignDoubleEvaluator(source, magnitude.get(context), sign.get(context), context); + } + + @Override + public String toString() { + return "CopySignDoubleEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignFloatEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignFloatEvaluator.java new file mode 100644 index 0000000000000..6a55a742d631c --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignFloatEvaluator.java @@ -0,0 +1,148 @@ +// 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.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.FloatBlock; +import org.elasticsearch.compute.data.FloatVector; +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.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link CopySign}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class CopySignFloatEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator magnitude; + + private final EvalOperator.ExpressionEvaluator sign; + + private final DriverContext driverContext; + + private Warnings warnings; + + public CopySignFloatEvaluator(Source source, EvalOperator.ExpressionEvaluator magnitude, + EvalOperator.ExpressionEvaluator sign, DriverContext driverContext) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (FloatBlock magnitudeBlock = (FloatBlock) magnitude.eval(page)) { + try (DoubleBlock signBlock = (DoubleBlock) sign.eval(page)) { + FloatVector magnitudeVector = magnitudeBlock.asVector(); + if (magnitudeVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + DoubleVector signVector = signBlock.asVector(); + if (signVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + return eval(page.getPositionCount(), magnitudeVector, signVector).asBlock(); + } + } + } + + public FloatBlock eval(int positionCount, FloatBlock magnitudeBlock, DoubleBlock signBlock) { + try(FloatBlock.Builder result = driverContext.blockFactory().newFloatBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (magnitudeBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (magnitudeBlock.getValueCount(p) != 1) { + if (magnitudeBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (signBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (signBlock.getValueCount(p) != 1) { + if (signBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendFloat(CopySign.processFloat(magnitudeBlock.getFloat(magnitudeBlock.getFirstValueIndex(p)), signBlock.getDouble(signBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public FloatVector eval(int positionCount, FloatVector magnitudeVector, DoubleVector signVector) { + try(FloatVector.FixedBuilder result = driverContext.blockFactory().newFloatVectorFixedBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendFloat(p, CopySign.processFloat(magnitudeVector.getFloat(p), signVector.getDouble(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "CopySignFloatEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(magnitude, sign); + } + + 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 magnitude; + + private final EvalOperator.ExpressionEvaluator.Factory sign; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory magnitude, + EvalOperator.ExpressionEvaluator.Factory sign) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + } + + @Override + public CopySignFloatEvaluator get(DriverContext context) { + return new CopySignFloatEvaluator(source, magnitude.get(context), sign.get(context), context); + } + + @Override + public String toString() { + return "CopySignFloatEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignIntegerEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignIntegerEvaluator.java new file mode 100644 index 0000000000000..e9db4a0f12506 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignIntegerEvaluator.java @@ -0,0 +1,148 @@ +// 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.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.IntVector; +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.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link CopySign}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class CopySignIntegerEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator magnitude; + + private final EvalOperator.ExpressionEvaluator sign; + + private final DriverContext driverContext; + + private Warnings warnings; + + public CopySignIntegerEvaluator(Source source, EvalOperator.ExpressionEvaluator magnitude, + EvalOperator.ExpressionEvaluator sign, DriverContext driverContext) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (IntBlock magnitudeBlock = (IntBlock) magnitude.eval(page)) { + try (DoubleBlock signBlock = (DoubleBlock) sign.eval(page)) { + IntVector magnitudeVector = magnitudeBlock.asVector(); + if (magnitudeVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + DoubleVector signVector = signBlock.asVector(); + if (signVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + return eval(page.getPositionCount(), magnitudeVector, signVector).asBlock(); + } + } + } + + public IntBlock eval(int positionCount, IntBlock magnitudeBlock, DoubleBlock signBlock) { + try(IntBlock.Builder result = driverContext.blockFactory().newIntBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (magnitudeBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (magnitudeBlock.getValueCount(p) != 1) { + if (magnitudeBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (signBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (signBlock.getValueCount(p) != 1) { + if (signBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendInt(CopySign.processInteger(magnitudeBlock.getInt(magnitudeBlock.getFirstValueIndex(p)), signBlock.getDouble(signBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public IntVector eval(int positionCount, IntVector magnitudeVector, DoubleVector signVector) { + try(IntVector.FixedBuilder result = driverContext.blockFactory().newIntVectorFixedBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendInt(p, CopySign.processInteger(magnitudeVector.getInt(p), signVector.getDouble(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "CopySignIntegerEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(magnitude, sign); + } + + 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 magnitude; + + private final EvalOperator.ExpressionEvaluator.Factory sign; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory magnitude, + EvalOperator.ExpressionEvaluator.Factory sign) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + } + + @Override + public CopySignIntegerEvaluator get(DriverContext context) { + return new CopySignIntegerEvaluator(source, magnitude.get(context), sign.get(context), context); + } + + @Override + public String toString() { + return "CopySignIntegerEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignLongEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignLongEvaluator.java new file mode 100644 index 0000000000000..4afbcedd3842a --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignLongEvaluator.java @@ -0,0 +1,148 @@ +// 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.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +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.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link CopySign}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class CopySignLongEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator magnitude; + + private final EvalOperator.ExpressionEvaluator sign; + + private final DriverContext driverContext; + + private Warnings warnings; + + public CopySignLongEvaluator(Source source, EvalOperator.ExpressionEvaluator magnitude, + EvalOperator.ExpressionEvaluator sign, DriverContext driverContext) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock magnitudeBlock = (LongBlock) magnitude.eval(page)) { + try (DoubleBlock signBlock = (DoubleBlock) sign.eval(page)) { + LongVector magnitudeVector = magnitudeBlock.asVector(); + if (magnitudeVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + DoubleVector signVector = signBlock.asVector(); + if (signVector == null) { + return eval(page.getPositionCount(), magnitudeBlock, signBlock); + } + return eval(page.getPositionCount(), magnitudeVector, signVector).asBlock(); + } + } + } + + public LongBlock eval(int positionCount, LongBlock magnitudeBlock, DoubleBlock signBlock) { + try(LongBlock.Builder result = driverContext.blockFactory().newLongBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (magnitudeBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (magnitudeBlock.getValueCount(p) != 1) { + if (magnitudeBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (signBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (signBlock.getValueCount(p) != 1) { + if (signBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendLong(CopySign.processLong(magnitudeBlock.getLong(magnitudeBlock.getFirstValueIndex(p)), signBlock.getDouble(signBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public LongVector eval(int positionCount, LongVector magnitudeVector, DoubleVector signVector) { + try(LongVector.FixedBuilder result = driverContext.blockFactory().newLongVectorFixedBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendLong(p, CopySign.processLong(magnitudeVector.getLong(p), signVector.getDouble(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "CopySignLongEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(magnitude, sign); + } + + 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 magnitude; + + private final EvalOperator.ExpressionEvaluator.Factory sign; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory magnitude, + EvalOperator.ExpressionEvaluator.Factory sign) { + this.source = source; + this.magnitude = magnitude; + this.sign = sign; + } + + @Override + public CopySignLongEvaluator get(DriverContext context) { + return new CopySignLongEvaluator(source, magnitude.get(context), sign.get(context), context); + } + + @Override + public String toString() { + return "CopySignLongEvaluator[" + "magnitude=" + magnitude + ", sign=" + sign + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 3dd2f8fb9ffce..dc1e7e9a8e424 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -1135,6 +1135,11 @@ public enum Cap { */ ROUND_TO, + /** + * Support for the {@code COPY_SIGN} function. + */ + COPY_SIGN, + /** * Allow lookup join on mixed numeric fields, among byte, short, int, long, half_float, scaled_float, float and double. */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index d18e66362d788..f5802cb72f624 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -93,6 +93,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan2; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cbrt; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Ceil; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.CopySign; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cos; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cosh; import org.elasticsearch.xpack.esql.expression.function.scalar.math.E; @@ -333,6 +334,7 @@ private static FunctionDefinition[][] functions() { def(Exp.class, Exp::new, "exp"), def(Floor.class, Floor::new, "floor"), def(Greatest.class, Greatest::new, "greatest"), + def(CopySign.class, bi(CopySign::new), "copy_sign"), def(Hypot.class, Hypot::new, "hypot"), def(Log.class, Log::new, "log"), def(Log10.class, Log10::new, "log10"), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java index 8cd0faade9cba..7913d5e11a5a5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java @@ -22,6 +22,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch; import org.elasticsearch.xpack.esql.expression.function.scalar.ip.IpPrefix; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan2; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.CopySign; import org.elasticsearch.xpack.esql.expression.function.scalar.math.E; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Hypot; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Log; @@ -71,6 +72,7 @@ public static List getNamedWriteables() { entries.add(EndsWith.ENTRY); entries.add(FromAggregateMetricDouble.ENTRY); entries.add(Greatest.ENTRY); + entries.add(CopySign.ENTRY); entries.add(Hash.ENTRY); entries.add(Hypot.ENTRY); entries.add(In.ENTRY); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java new file mode 100644 index 0000000000000..884c67c38a3f9 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java @@ -0,0 +1,191 @@ +/* + * 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.function.scalar.math; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.compute.ann.Evaluator; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.Expressions; +import org.elasticsearch.xpack.esql.core.tree.NodeInfo; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; +import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * ES|QL function that mimics the behavior of {@code Math.copySign(double magnitude, double sign)}. + * Returns a value with the magnitude of the first argument and the sign of the second argument. + * + *

+ * The output of this function is the MAGNITUDE with the SIGN from `sign` applied to it. + * For that reason, we cast the SIGN to DOUBLE, which is the most general numeric type, + * and allows us to write a single check for all possible types of `sign`. + * However, the output type of this function is determined by the `magnitude` type. + */ +public class CopySign extends EsqlScalarFunction { + + public static final String NAME = "copy_sign"; + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, NAME, CopySign::new); + + private interface CopySignFactoryProvider { + EvalOperator.ExpressionEvaluator.Factory create( + Source source, + EvalOperator.ExpressionEvaluator.Factory magnitude, + EvalOperator.ExpressionEvaluator.Factory sign + ); + } + + private static final Map FACTORY_PROVIDERS = Map.ofEntries( + Map.entry(DataType.FLOAT, CopySignFloatEvaluator.Factory::new), + Map.entry(DataType.DOUBLE, CopySignDoubleEvaluator.Factory::new), + Map.entry(DataType.LONG, CopySignLongEvaluator.Factory::new), + Map.entry(DataType.INTEGER, CopySignIntegerEvaluator.Factory::new) + ); + + private DataType dataType; + + @FunctionInfo( + description = "Returns a value with the magnitude of the first argument and the sign of the second argument. " + + "This function is similar to Java's Math.copySign(double magnitude, double sign).", + returnType = { "double", "float" } + ) + public CopySign( + Source source, + @Param( + name = "magnitude", + type = { "double", "float", "integer", "long" }, + description = "The expression providing the magnitude of the result. Must be a numeric type." + ) Expression magnitude, + @Param( + name = "sign", + type = { "double", "float", "integer", "long" }, + description = "The expression providing the sign of the result. Must be a numeric type." + ) Expression sign + ) { + super(source, Arrays.asList(magnitude, sign)); + } + + private CopySign(StreamInput in) throws IOException { + this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + source().writeTo(out); + out.writeNamedWriteable(children().get(0)); + out.writeNamedWriteable(children().get(1)); + } + + @Override + public String getWriteableName() { + return ENTRY.name; + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, CopySign::new, children().get(0), children().get(1)); + } + + @Override + public Expression replaceChildren(List newChildren) { + if (newChildren.size() != 2) { + throw new EsqlIllegalArgumentException("Function [{}] expects exactly two arguments, got [{}]", NAME, newChildren.size()); + } + return new CopySign(source(), newChildren.get(0), newChildren.get(1)); + } + + @Override + public DataType dataType() { + if (dataType == null) { + resolveType(); + } + return dataType; + } + + @Override + public TypeResolution resolveType() { + if (childrenResolved() == false) { + return new TypeResolution("Unresolved children"); + } + var magnitude = children().get(0); + var sign = children().get(1); + if (magnitude.dataType().isNumeric() == false) { + return new TypeResolution("Magnitude must be a numeric type"); + } + if (sign.dataType().isNumeric() == false) { + return new TypeResolution("Sign must be a numeric type"); + } + // The return type is the same as the magnitude type, so we can use it directly. + dataType = magnitude.dataType(); + return TypeResolution.TYPE_RESOLVED; + } + + @Override + public boolean foldable() { + return Expressions.foldable(children()); + } + + @Override + public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { + var dataType = dataType(); + if (FACTORY_PROVIDERS.containsKey(dataType) == false) { + throw new EsqlIllegalArgumentException("Unsupported data type [{}] for function [{}]", dataType(), NAME); + } + var sign = children().get(1); + var magnitude = children().get(0); + // The output of this function is the MAGNITUDE with the SIGN from `sign` applied to it. + // For that reason, we cast the SIGN to DOUBLE, which is the most general numeric type, + // and allows us to write a single check (<0 or >=0) for all possible types of `sign`. + // However, the output type of this function is determined by the `magnitude` type. + return FACTORY_PROVIDERS.get(dataType) + .create(source(), toEvaluator.apply(magnitude), Cast.cast(source(), sign.dataType(), DataType.DOUBLE, toEvaluator.apply(sign))); + } + + @Evaluator(extraName = "Float") + static float processFloat(float magnitude, double sign) { + if (sign < 0) { + return magnitude < 0 ? magnitude : -magnitude; + } else { + return magnitude < 0 ? -magnitude : magnitude; + } + } + + @Evaluator(extraName = "Double") + static double processDouble(double magnitude, double sign) { + return Math.copySign(magnitude, sign); + } + + @Evaluator(extraName = "Long") + static long processLong(long magnitude, double sign) { + if (sign < 0) { + return magnitude < 0 ? magnitude : -magnitude; + } else { + return magnitude < 0 ? -magnitude : magnitude; + } + } + + @Evaluator(extraName = "Integer") + static int processInteger(int magnitude, double sign) { + if (sign < 0) { + return magnitude < 0 ? magnitude : -magnitude; + } else { + return magnitude < 0 ? -magnitude : magnitude; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/package-info.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/package-info.java index 19dbb2deae780..280b432140ddf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/package-info.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/package-info.java @@ -93,9 +93,9 @@ *

  • Oracle * is pretty good about dates. It's fine about a lot of things but PostgreSQL is better.
  • *
  • MS SQL Server - * has a silly name but it's documentation is wonderful.
  • + * has a silly name but its documentation is wonderful. *
  • SPL - * is super familiar to our users and it's a piped query language.
  • + * is super familiar to our users, and is a piped query language. * * *

    Major Components

    diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml index 6b3a9bedb5a01..4cb86e8e01a0e 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml @@ -130,7 +130,7 @@ setup: - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} # Testing for the entire function set isn't feasible, so we just check that we return the correct count as an approximation. - - length: {esql.functions: 155} # check the "sister" test below for a likely update to the same esql.functions length check + - length: {esql.functions: 156} # check the "sister" test below for a likely update to the same esql.functions length check --- "Basic ESQL usage output (telemetry) non-snapshot version": @@ -228,7 +228,7 @@ setup: - gt: {esql.functions.to_long: $functions_to_long} - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} - - length: {esql.functions: 143} # check the "sister" test above for a likely update to the same esql.functions length check + - length: {esql.functions: 144} # check the "sister" test above for a likely update to the same esql.functions length check --- took: