Skip to content

Commit 0049f1c

Browse files
committed
SQL: rewrite ROUND and TRUNCATE functions with a different optional parameter handling method (#40242)
* Rewrite Round and Truncate functions to have a slightly different approach to handling the optional parameter in the constructor. Until now the optional parameter was considered 0 if the value was missing and the constructor was filling in this value. The current solution is to have the optional parameter as null right until the actual calculation is done. (cherry picked from commit 3e314f8)
1 parent 12cfacb commit 0049f1c

File tree

17 files changed

+548
-55
lines changed

17 files changed

+548
-55
lines changed

x-pack/plugin/sql/qa/src/main/resources/agg.csv-spec

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,23 @@ ROUND(SQRT(CAST(EXP(languages) AS SMALLINT)),2):d| COUNT(1):l
192192
null |10
193193
;
194194

195+
groupByRoundWithTwoParams
196+
SELECT ROUND(YEAR("birth_date"), -2) FROM test_emp GROUP BY ROUND(YEAR("birth_date"), -2);
197+
198+
ROUND(YEAR(birth_date [Z]),-2)
199+
-----------------------------
200+
null
201+
2000
202+
;
203+
204+
groupByTruncateWithTwoParams
205+
SELECT TRUNCATE(YEAR("birth_date"), -2) FROM test_emp GROUP BY TRUNCATE(YEAR("birth_date"), -2);
206+
207+
TRUNCATE(YEAR(birth_date [Z]),-2)
208+
--------------------------------
209+
null
210+
1900
211+
;
195212

196213
//
197214
// Grouping functions

x-pack/plugin/sql/qa/src/main/resources/agg.sql-spec

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,31 @@ SELECT emp_no * 2 AS e FROM test_emp GROUP BY e ORDER BY e;
5050
groupByModScalar
5151
SELECT (emp_no % 3) + 1 AS e FROM test_emp GROUP BY e ORDER BY e;
5252

53+
// group by nested functions with no alias
54+
//https://github.com/elastic/elasticsearch/issues/40239
55+
groupByTruncate-Ignore
56+
SELECT CAST(TRUNCATE(EXTRACT(YEAR FROM "birth_date")) AS INTEGER) AS x FROM test_emp GROUP BY CAST(TRUNCATE(EXTRACT(YEAR FROM "birth_date")) AS INTEGER) ORDER BY CAST(TRUNCATE(EXTRACT(YEAR FROM "birth_date")) AS INTEGER);
57+
//https://github.com/elastic/elasticsearch/issues/40239
58+
groupByRound-Ignore
59+
SELECT CAST(ROUND(EXTRACT(YEAR FROM "birth_date")) AS INTEGER) AS x FROM test_emp GROUP BY CAST(ROUND(EXTRACT(YEAR FROM "birth_date")) AS INTEGER) ORDER BY CAST(ROUND(EXTRACT(YEAR FROM "birth_date")) AS INTEGER);
60+
groupByAtan2
61+
SELECT ATAN2(YEAR("birth_date"), 5) AS x FROM test_emp GROUP BY ATAN2(YEAR("birth_date"), 5) ORDER BY ATAN2(YEAR("birth_date"), 5);
62+
groupByPower
63+
SELECT POWER(YEAR("birth_date"), 2) AS x FROM test_emp GROUP BY POWER(YEAR("birth_date"), 2) ORDER BY POWER(YEAR("birth_date"), 2);
64+
//https://github.com/elastic/elasticsearch/issues/40239
65+
groupByPowerWithCast-Ignore
66+
SELECT CAST(POWER(YEAR("birth_date"), 2) AS DOUBLE) AS x FROM test_emp GROUP BY CAST(POWER(YEAR("birth_date"), 2) AS DOUBLE) ORDER BY CAST(POWER(YEAR("birth_date"), 2) AS DOUBLE);
67+
groupByConcat
68+
SELECT LEFT(CONCAT("first_name", "last_name"), 3) AS x FROM test_emp GROUP BY LEFT(CONCAT("first_name", "last_name"), 3) ORDER BY LEFT(CONCAT("first_name", "last_name"), 3) LIMIT 15;
69+
groupByLocateWithTwoParams
70+
SELECT LOCATE('a', CONCAT("first_name", "last_name")) AS x FROM test_emp GROUP BY LOCATE('a', CONCAT("first_name", "last_name")) ORDER BY LOCATE('a', CONCAT("first_name", "last_name"));
71+
groupByLocateWithThreeParams
72+
SELECT LOCATE('a', CONCAT("first_name", "last_name"), 3) AS x FROM test_emp GROUP BY LOCATE('a', CONCAT("first_name", "last_name"), 3) ORDER BY LOCATE('a', CONCAT("first_name", "last_name"), 3);
73+
groupByRoundAndTruncateWithTwoParams
74+
SELECT ROUND(SIN(TRUNCATE("salary", 2)), 2) AS x FROM "test_emp" GROUP BY ROUND(SIN(TRUNCATE("salary", 2)), 2) ORDER BY ROUND(SIN(TRUNCATE("salary", 2)), 2) LIMIT 5;
75+
groupByRoundAndTruncateWithOneParam
76+
SELECT ROUND(SIN(TRUNCATE(languages))) AS x FROM "test_emp" GROUP BY ROUND(SIN(TRUNCATE(languages))) ORDER BY ROUND(SIN(TRUNCATE(languages))) LIMIT 5;
77+
5378
// multiple group by
5479
groupByMultiOnText
5580
SELECT gender g, languages l FROM "test_emp" GROUP BY gender, languages ORDER BY gender ASC, languages ASC;

x-pack/plugin/sql/qa/src/main/resources/math.csv-spec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ null |10
1515
truncateWithNoSecondParameterWithAsciiHavingAndOrderBy
1616
SELECT TRUNCATE(ASCII(LEFT(first_name, 1))), COUNT(*) count FROM test_emp GROUP BY ASCII(LEFT(first_name, 1)) HAVING COUNT(*) > 5 ORDER BY TRUNCATE(ASCII(LEFT(first_name, 1))) DESC;
1717

18-
TRUNCATE(ASCII(LEFT(first_name,1)),0):i| count:l
18+
TRUNCATE(ASCII(LEFT(first_name,1))):i| count:l
1919
---------------------------------------+---------------
2020
null |10
2121
66 |7
@@ -163,7 +163,7 @@ ROUND(AVG(salary),2):d| AVG(salary):d | COUNT(1):l
163163
groupByAndOrderByRoundWithNoSecondParameter
164164
SELECT ROUND(AVG(salary)), ROUND(salary) rounded, AVG(salary), COUNT(*) FROM test_emp GROUP BY rounded ORDER BY rounded DESC LIMIT 10;
165165

166-
ROUND(AVG(salary),0):d| rounded:i | AVG(salary):d | COUNT(1):l
166+
ROUND(AVG(salary)):d| rounded:i | AVG(salary):d | COUNT(1):l
167167
----------------------+---------------+---------------+---------------
168168
74999.0 |74999 |74999.0 |1
169169
74970.0 |74970 |74970.0 |1

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NonIsoDateTimeProcessor;
1313
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
1414
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor;
15+
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryOptionalMathProcessor;
1516
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor;
1617
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor;
1718
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor;
@@ -68,7 +69,6 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
6869
// arithmetic
6970
entries.add(new Entry(Processor.class, BinaryArithmeticProcessor.NAME, BinaryArithmeticProcessor::new));
7071
entries.add(new Entry(Processor.class, UnaryArithmeticProcessor.NAME, UnaryArithmeticProcessor::new));
71-
entries.add(new Entry(Processor.class, BinaryMathProcessor.NAME, BinaryMathProcessor::new));
7272
// comparators
7373
entries.add(new Entry(Processor.class, BinaryComparisonProcessor.NAME, BinaryComparisonProcessor::new));
7474
entries.add(new Entry(Processor.class, InProcessor.NAME, InProcessor::new));
@@ -82,6 +82,8 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
8282
entries.add(new Entry(Processor.class, NonIsoDateTimeProcessor.NAME, NonIsoDateTimeProcessor::new));
8383
entries.add(new Entry(Processor.class, QuarterProcessor.NAME, QuarterProcessor::new));
8484
// math
85+
entries.add(new Entry(Processor.class, BinaryMathProcessor.NAME, BinaryMathProcessor::new));
86+
entries.add(new Entry(Processor.class, BinaryOptionalMathProcessor.NAME, BinaryOptionalMathProcessor::new));
8587
entries.add(new Entry(Processor.class, MathProcessor.NAME, MathProcessor::new));
8688
// string
8789
entries.add(new Entry(Processor.class, StringProcessor.NAME, StringProcessor::new));

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,7 @@ public enum BinaryMathOperation implements BiFunction<Number, Number, Number> {
2525

2626
ATAN2((l, r) -> Math.atan2(l.doubleValue(), r.doubleValue())),
2727
MOD(Arithmetics::mod),
28-
POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue())),
29-
ROUND((l, r) -> {
30-
if (r instanceof Float || r instanceof Double) {
31-
throw new SqlIllegalArgumentException("An integer number is required; received [{}] as second parameter", r);
32-
}
33-
34-
double tenAtScale = Math.pow(10., r.longValue());
35-
double middleResult = l.doubleValue() * tenAtScale;
36-
int sign = middleResult > 0 ? 1 : -1;
37-
return Math.round(Math.abs(middleResult)) / tenAtScale * sign;
38-
}),
39-
TRUNCATE((l, r) -> {
40-
if (r instanceof Float || r instanceof Double) {
41-
throw new SqlIllegalArgumentException("An integer number is required; received [{}] as second parameter", r);
42-
}
43-
44-
double tenAtScale = Math.pow(10., r.longValue());
45-
double g = l.doubleValue() * tenAtScale;
46-
return (((l.doubleValue() < 0) ? Math.ceil(g) : Math.floor(g)) / tenAtScale);
47-
});
28+
POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue()));
4829

4930
private final BiFunction<Number, Number, Number> process;
5031

@@ -79,7 +60,7 @@ public String getWriteableName() {
7960
@Override
8061
protected void checkParameter(Object param) {
8162
if (!(param instanceof Number)) {
82-
throw new SqlIllegalArgumentException("A number is required; received {}", param);
63+
throw new SqlIllegalArgumentException("A number is required; received [{}]", param);
8364
}
8465
}
8566
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericFunction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,6 @@ public boolean equals(Object obj) {
6969
BinaryNumericFunction other = (BinaryNumericFunction) obj;
7070
return Objects.equals(other.left(), left())
7171
&& Objects.equals(other.right(), right())
72-
&& Objects.equals(other.operation, operation);
72+
&& Objects.equals(other.operation, operation);
7373
}
7474
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.sql.expression.function.scalar.math;
8+
9+
import org.elasticsearch.xpack.sql.execution.search.SqlSourceBuilder;
10+
import org.elasticsearch.xpack.sql.expression.Expression;
11+
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryOptionalMathProcessor.BinaryOptionalMathOperation;
12+
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
13+
import org.elasticsearch.xpack.sql.tree.NodeInfo;
14+
import org.elasticsearch.xpack.sql.tree.Location;
15+
16+
import java.util.Arrays;
17+
import java.util.List;
18+
import java.util.Objects;
19+
20+
public class BinaryOptionalMathPipe extends Pipe {
21+
22+
private final Pipe left, right;
23+
private final BinaryOptionalMathOperation operation;
24+
25+
public BinaryOptionalMathPipe(Location location, Expression expression, Pipe left, Pipe right, BinaryOptionalMathOperation operation) {
26+
super(location, expression, right == null ? Arrays.asList(left) : Arrays.asList(left, right));
27+
this.left = left;
28+
this.right = right;
29+
this.operation = operation;
30+
}
31+
32+
@Override
33+
public final Pipe replaceChildren(List<Pipe> newChildren) {
34+
int childrenSize = newChildren.size();
35+
if (childrenSize > 2 || childrenSize < 1) {
36+
throw new IllegalArgumentException("expected [1 or 2] children but received [" + newChildren.size() + "]");
37+
}
38+
return replaceChildren(newChildren.get(0), childrenSize == 1 ? null : newChildren.get(1));
39+
}
40+
41+
@Override
42+
public final Pipe resolveAttributes(AttributeResolver resolver) {
43+
Pipe newLeft = left.resolveAttributes(resolver);
44+
Pipe newRight = right == null ? right : right.resolveAttributes(resolver);
45+
if (newLeft == left && newRight == right) {
46+
return this;
47+
}
48+
return replaceChildren(newLeft, newRight);
49+
}
50+
51+
@Override
52+
public boolean supportedByAggsOnlyQuery() {
53+
return right == null ? left.supportedByAggsOnlyQuery() : left.supportedByAggsOnlyQuery() || right.supportedByAggsOnlyQuery();
54+
}
55+
56+
@Override
57+
public boolean resolved() {
58+
return left.resolved() && (right == null || right.resolved());
59+
}
60+
61+
protected Pipe replaceChildren(Pipe newLeft, Pipe newRight) {
62+
return new BinaryOptionalMathPipe(location(), expression(), newLeft, newRight, operation);
63+
}
64+
65+
@Override
66+
public final void collectFields(SqlSourceBuilder sourceBuilder) {
67+
left.collectFields(sourceBuilder);
68+
if (right != null) {
69+
right.collectFields(sourceBuilder);
70+
}
71+
}
72+
73+
@Override
74+
protected NodeInfo<BinaryOptionalMathPipe> info() {
75+
return NodeInfo.create(this, BinaryOptionalMathPipe::new, expression(), left, right, operation);
76+
}
77+
78+
@Override
79+
public BinaryOptionalMathProcessor asProcessor() {
80+
return new BinaryOptionalMathProcessor(left.asProcessor(), right == null ? null : right.asProcessor(), operation);
81+
}
82+
83+
public Pipe right() {
84+
return right;
85+
}
86+
87+
public Pipe left() {
88+
return left;
89+
}
90+
91+
public BinaryOptionalMathOperation operation() {
92+
return operation;
93+
}
94+
95+
@Override
96+
public int hashCode() {
97+
return Objects.hash(left, right, operation);
98+
}
99+
100+
@Override
101+
public boolean equals(Object obj) {
102+
if (this == obj) {
103+
return true;
104+
}
105+
106+
if (obj == null || getClass() != obj.getClass()) {
107+
return false;
108+
}
109+
110+
BinaryOptionalMathPipe other = (BinaryOptionalMathPipe) obj;
111+
return Objects.equals(left, other.left)
112+
&& Objects.equals(right, other.right)
113+
&& Objects.equals(operation, other.operation);
114+
}
115+
}

0 commit comments

Comments
 (0)