1
0
Эх сурвалжийг харах

ESQL: date_trunc(): swap order of arguments (#98624)

Swap arguments order so that the range parameter is first and datetime
one second, inline with other languages.
Bogdan Pintea 2 жил өмнө
parent
commit
372458c9fd

+ 1 - 1
benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java

@@ -94,7 +94,7 @@ public class EvalBenchmark {
                     new EsField("timestamp", DataTypes.DATETIME, Map.of(), true)
                     new EsField("timestamp", DataTypes.DATETIME, Map.of(), true)
                 );
                 );
                 yield EvalMapper.toEvaluator(
                 yield EvalMapper.toEvaluator(
-                    new DateTrunc(Source.EMPTY, timestamp, new Literal(Source.EMPTY, Duration.ofHours(24), EsqlDataTypes.TIME_DURATION)),
+                    new DateTrunc(Source.EMPTY, new Literal(Source.EMPTY, Duration.ofHours(24), EsqlDataTypes.TIME_DURATION), timestamp),
                     layout(timestamp)
                     layout(timestamp)
                 ).get();
                 ).get();
             }
             }

+ 1 - 1
docs/reference/esql/functions/date_trunc.asciidoc

@@ -6,7 +6,7 @@ Rounds down a date to the closest interval. Intervals can be expressed using the
 [source,esql]
 [source,esql]
 ----
 ----
 FROM employees
 FROM employees
-| EVAL year_hired = DATE_TRUNC(hire_date, 1 year)
+| EVAL year_hired = DATE_TRUNC(1 year, hire_date)
 | STATS count(emp_no) BY year_hired
 | STATS count(emp_no) BY year_hired
 | SORT year_hired
 | SORT year_hired
 ----
 ----

+ 2 - 2
docs/reference/esql/index.asciidoc

@@ -46,7 +46,7 @@ POST /_query
 {
 {
   "query": """
   "query": """
     FROM library
     FROM library
-    | EVAL year = DATE_TRUNC(release_date, 1 YEARS)
+    | EVAL year = DATE_TRUNC(1 YEARS, release_date)
     | STATS MAX(page_count) BY year
     | STATS MAX(page_count) BY year
     | SORT year
     | SORT year
     | LIMIT 5
     | LIMIT 5
@@ -83,7 +83,7 @@ POST /_query?format=txt
 {
 {
   "query": """
   "query": """
     FROM library
     FROM library
-    | EVAL year = DATE_TRUNC(release_date, 1 YEARS)
+    | EVAL year = DATE_TRUNC(1 YEARS, release_date)
     | STATS MAX(page_count) BY year
     | STATS MAX(page_count) BY year
     | SORT year
     | SORT year
     | LIMIT 5
     | LIMIT 5

+ 7 - 7
x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec

@@ -87,7 +87,7 @@ min:date                  | max:date
 ;
 ;
 
 
 evalDateTruncIntervalExpressionPeriod
 evalDateTruncIntervalExpressionPeriod
-from employees | sort hire_date | eval x = date_trunc(hire_date, 1 month) | keep emp_no, hire_date, x | limit 5;
+from employees | sort hire_date | eval x = date_trunc(1 month, hire_date) | keep emp_no, hire_date, x | limit 5;
 
 
 emp_no:integer | hire_date:date                | x:date                    
 emp_no:integer | hire_date:date                | x:date                    
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-01T00:00:00.000Z         
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-01T00:00:00.000Z         
@@ -98,7 +98,7 @@ emp_no:integer | hire_date:date                | x:date
 ;
 ;
 
 
 evalDateTruncIntervalExpressionDuration
 evalDateTruncIntervalExpressionDuration
-from employees | sort hire_date | eval x = date_trunc(hire_date, 240 hours) | keep emp_no, hire_date, x | limit 5;
+from employees | sort hire_date | eval x = date_trunc(240 hours, hire_date) | keep emp_no, hire_date, x | limit 5;
 
 
 emp_no:integer | hire_date:date                | x:date                  
 emp_no:integer | hire_date:date                | x:date                  
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-11T00:00:00.000Z       
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-11T00:00:00.000Z       
@@ -109,7 +109,7 @@ emp_no:integer | hire_date:date                | x:date
 ;
 ;
 
 
 evalDateTruncWeeklyInterval
 evalDateTruncWeeklyInterval
-from employees | sort hire_date | eval x = date_trunc(hire_date, 1 week) | keep emp_no, hire_date, x | limit 5;
+from employees | sort hire_date | eval x = date_trunc(1 week, hire_date) | keep emp_no, hire_date, x | limit 5;
 
 
 emp_no:integer | hire_date:date                | x:date                  
 emp_no:integer | hire_date:date                | x:date                  
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-18T00:00:00.000Z       
 10009          | 1985-02-18T00:00:00.000Z      | 1985-02-18T00:00:00.000Z       
@@ -120,7 +120,7 @@ emp_no:integer | hire_date:date                | x:date
 ;
 ;
 
 
 evalDateTruncQuarterlyInterval
 evalDateTruncQuarterlyInterval
-from employees | sort hire_date | eval x = date_trunc(hire_date, 3 month) | keep emp_no, hire_date, x | limit 5;
+from employees | sort hire_date | eval x = date_trunc(3 month, hire_date) | keep emp_no, hire_date, x | limit 5;
 
 
 emp_no:integer | hire_date:date                | x:date                    
 emp_no:integer | hire_date:date                | x:date                    
 10009          | 1985-02-18T00:00:00.000Z      | 1985-01-01T00:00:00.000Z         
 10009          | 1985-02-18T00:00:00.000Z      | 1985-01-01T00:00:00.000Z         
@@ -131,14 +131,14 @@ emp_no:integer | hire_date:date                | x:date
 ;
 ;
 
 
 evalDateTruncNullDate
 evalDateTruncNullDate
-from employees | where emp_no == 10040 | eval x = date_trunc(birth_date, 1 day) | keep emp_no, birth_date, x;
+from employees | where emp_no == 10040 | eval x = date_trunc(1 day, birth_date) | keep emp_no, birth_date, x;
 
 
 emp_no:integer | birth_date:date               | x:date
 emp_no:integer | birth_date:date               | x:date
 10040          | null                          | null  
 10040          | null                          | null  
 ;
 ;
 
 
 evalDateTruncGrouping
 evalDateTruncGrouping
-from employees | eval y = date_trunc(hire_date, 1 year) | stats count(emp_no) by y | sort y | keep y, count(emp_no) | limit 5;
+from employees | eval y = date_trunc(1 year, hire_date) | stats count(emp_no) by y | sort y | keep y, count(emp_no) | limit 5;
 
 
 y:date                        | count(emp_no):long
 y:date                        | count(emp_no):long
 1985-01-01T00:00:00.000Z      | 11       
 1985-01-01T00:00:00.000Z      | 11       
@@ -149,7 +149,7 @@ y:date                        | count(emp_no):long
 ;
 ;
 
 
 in
 in
-from employees | eval x = date_trunc(hire_date, 1 year) | where birth_date not in (x, hire_date) | keep x, hire_date | sort x desc | limit 4;
+from employees | eval x = date_trunc(1 year, hire_date) | where birth_date not in (x, hire_date) | keep x, hire_date | sort x desc | limit 4;
 
 
 x:date                  |hire_date:date
 x:date                  |hire_date:date
 1999-01-01T00:00:00.000Z|1999-04-30T00:00:00.000Z
 1999-01-01T00:00:00.000Z|1999-04-30T00:00:00.000Z

+ 2 - 2
x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats.csv-spec

@@ -392,7 +392,7 @@ c:long | languages:integer | still_hired:boolean
 ;
 ;
 
 
 byDateAndKeywordAndInt
 byDateAndKeywordAndInt
-from employees | eval d = date_trunc(hire_date, 1 year) | stats c = count(emp_no) by d, gender, languages | sort c desc, d, languages desc | limit 10;
+from employees | eval d = date_trunc(1 year, hire_date) | stats c = count(emp_no) by d, gender, languages | sort c desc, d, languages desc | limit 10;
 
 
 c:long |           d:date         | gender:keyword | languages:integer
 c:long |           d:date         | gender:keyword | languages:integer
      3 | 1986-01-01T00:00:00.000Z | M              | 2
      3 | 1986-01-01T00:00:00.000Z | M              | 2
@@ -408,7 +408,7 @@ c:long |           d:date         | gender:keyword | languages:integer
 ;
 ;
 
 
 byDateAndKeywordAndIntWithAlias
 byDateAndKeywordAndIntWithAlias
-from employees | eval d = date_trunc(hire_date, 1 year) | rename gender as g, languages as l, emp_no as e | keep d, g, l, e | stats c = count(e) by d, g, l | sort c desc, d, l desc | limit 10;
+from employees | eval d = date_trunc(1 year, hire_date) | rename gender as g, languages as l, emp_no as e | keep d, g, l, e | stats c = count(e) by d, g, l | sort c desc, d, l desc | limit 10;
 
 
 c:long |           d:date         | g:keyword | l:integer
 c:long |           d:date         | g:keyword | l:integer
      3 | 1986-01-01T00:00:00.000Z | M         | 2
      3 | 1986-01-01T00:00:00.000Z | M         | 2

+ 1 - 1
x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_count_distinct.csv-spec

@@ -101,7 +101,7 @@ COUNT_DISTINCT(ip0,80000):long | COUNT_DISTINCT(ip1,5):long
 ;
 ;
 
 
 countDistinctOfDates
 countDistinctOfDates
-from employees | eval d = date_trunc(hire_date, 1 year) | stats h = count_distinct(d);  
+from employees | eval d = date_trunc(1 year, hire_date) | stats h = count_distinct(d);  
 
 
 h:long
 h:long
 14
 14

+ 3 - 3
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/BinaryDateTimeFunction.java

@@ -25,8 +25,8 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
 
 
     private final ZoneId zoneId;
     private final ZoneId zoneId;
 
 
-    protected BinaryDateTimeFunction(Source source, Expression timestamp, Expression argument) {
-        super(source, timestamp, argument);
+    protected BinaryDateTimeFunction(Source source, Expression argument, Expression timestamp) {
+        super(source, argument, timestamp);
         zoneId = DEFAULT_TZ;
         zoneId = DEFAULT_TZ;
     }
     }
 
 
@@ -36,7 +36,7 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
     }
     }
 
 
     public Expression timestampField() {
     public Expression timestampField() {
-        return left();
+        return right();
     }
     }
 
 
     public ZoneId zoneId() {
     public ZoneId zoneId() {

+ 23 - 5
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java

@@ -19,6 +19,8 @@ import org.elasticsearch.xpack.ql.expression.TypeResolutions;
 import org.elasticsearch.xpack.ql.expression.function.scalar.BinaryScalarFunction;
 import org.elasticsearch.xpack.ql.expression.function.scalar.BinaryScalarFunction;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
 import org.elasticsearch.xpack.ql.tree.Source;
+import org.elasticsearch.xpack.ql.type.DataType;
+import org.elasticsearch.xpack.ql.type.DataTypes;
 
 
 import java.time.Duration;
 import java.time.Duration;
 import java.time.Period;
 import java.time.Period;
@@ -27,6 +29,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
+import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.FIRST;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.FIRST;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.SECOND;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.SECOND;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isDate;
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isDate;
@@ -34,8 +37,8 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isType;
 
 
 public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper {
 public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper {
 
 
-    public DateTrunc(Source source, Expression field, Expression interval) {
-        super(source, field, interval);
+    public DateTrunc(Source source, Expression interval, Expression field) {
+        super(source, interval, field);
     }
     }
 
 
     @Override
     @Override
@@ -44,7 +47,12 @@ public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper
             return new TypeResolution("Unresolved children");
             return new TypeResolution("Unresolved children");
         }
         }
 
 
-        TypeResolution resolution = isDate(timestampField(), sourceText(), FIRST);
+        TypeResolution resolution = argumentTypesAreSwapped();
+        if (resolution.unresolved()) {
+            return resolution;
+        }
+
+        resolution = isDate(timestampField(), sourceText(), FIRST);
         if (resolution.unresolved()) {
         if (resolution.unresolved()) {
             return resolution;
             return resolution;
         }
         }
@@ -52,6 +60,16 @@ public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper
         return isInterval(interval(), sourceText(), SECOND);
         return isInterval(interval(), sourceText(), SECOND);
     }
     }
 
 
+    // TODO: drop check once 8.11 is released
+    private TypeResolution argumentTypesAreSwapped() {
+        DataType leftType = left().dataType();
+        DataType rightType = right().dataType();
+        if (leftType == DataTypes.DATETIME && (rightType == EsqlDataTypes.DATE_PERIOD || rightType == EsqlDataTypes.TIME_DURATION)) {
+            return new TypeResolution(format(null, "function definition has been updated, please swap arguments in [{}]", sourceText()));
+        }
+        return TypeResolution.TYPE_RESOLVED;
+    }
+
     private static TypeResolution isInterval(Expression e, String operationName, TypeResolutions.ParamOrdinal paramOrd) {
     private static TypeResolution isInterval(Expression e, String operationName, TypeResolutions.ParamOrdinal paramOrd) {
         return isType(
         return isType(
             e,
             e,
@@ -80,11 +98,11 @@ public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper
 
 
     @Override
     @Override
     protected NodeInfo<? extends Expression> info() {
     protected NodeInfo<? extends Expression> info() {
-        return NodeInfo.create(this, DateTrunc::new, timestampField(), interval());
+        return NodeInfo.create(this, DateTrunc::new, interval(), timestampField());
     }
     }
 
 
     public Expression interval() {
     public Expression interval() {
-        return right();
+        return left();
     }
     }
 
 
     static Rounding.Prepared createRounding(final Object interval) {
     static Rounding.Prepared createRounding(final Object interval) {

+ 15 - 8
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java

@@ -948,29 +948,36 @@ public class AnalyzerTests extends ESTestCase {
     public void testDateTruncOnInt() {
     public void testDateTruncOnInt() {
         verifyUnsupported("""
         verifyUnsupported("""
             from test
             from test
-            | eval date_trunc(int, "1M")
-            """, "first argument of [date_trunc(int, \"1M\")] must be [datetime], found value [int] type [integer]");
+            | eval date_trunc("1M", int)
+            """, "first argument of [date_trunc(\"1M\", int)] must be [datetime], found value [int] type [integer]");
     }
     }
 
 
     public void testDateTruncOnFloat() {
     public void testDateTruncOnFloat() {
         verifyUnsupported("""
         verifyUnsupported("""
             from test
             from test
-            | eval date_trunc(float, "1M")
-            """, "first argument of [date_trunc(float, \"1M\")] must be [datetime], found value [float] type [double]");
+            | eval date_trunc("1M", float)
+            """, "first argument of [date_trunc(\"1M\", float)] must be [datetime], found value [float] type [double]");
     }
     }
 
 
     public void testDateTruncOnText() {
     public void testDateTruncOnText() {
         verifyUnsupported("""
         verifyUnsupported("""
             from test
             from test
-            | eval date_trunc(keyword, "1M")
-            """, "first argument of [date_trunc(keyword, \"1M\")] must be [datetime], found value [keyword] type [keyword]");
+            | eval date_trunc("1M", keyword)
+            """, "first argument of [date_trunc(\"1M\", keyword)] must be [datetime], found value [keyword] type [keyword]");
     }
     }
 
 
     public void testDateTruncWithNumericInterval() {
     public void testDateTruncWithNumericInterval() {
         verifyUnsupported("""
         verifyUnsupported("""
             from test
             from test
-            | eval date_trunc(date, 1)
-            """, "second argument of [date_trunc(date, 1)] must be [dateperiod or timeduration], found value [1] type [integer]");
+            | eval date_trunc(1, date)
+            """, "second argument of [date_trunc(1, date)] must be [dateperiod or timeduration], found value [1] type [integer]");
+    }
+
+    public void testDateTruncWithSwappedArguments() {
+        verifyUnsupported("""
+            from test
+            | eval date_trunc(date, 1 month)
+            """, "function definition has been updated, please swap arguments in [date_trunc(date, 1 month)]");
     }
     }
 
 
     public void testDateTruncWithDateInterval() {
     public void testDateTruncWithDateInterval() {

+ 1 - 1
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncTests.java

@@ -134,7 +134,7 @@ public class DateTruncTests extends ESTestCase {
     }
     }
 
 
     public void testSerialization() {
     public void testSerialization() {
-        var dateTrunc = new DateTrunc(Source.EMPTY, randomDateField(), randomDateIntervalLiteral());
+        var dateTrunc = new DateTrunc(Source.EMPTY, randomDateIntervalLiteral(), randomDateField());
         SerializationTestUtils.assertSerialization(dateTrunc);
         SerializationTestUtils.assertSerialization(dateTrunc);
     }
     }
 
 

+ 1 - 1
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java

@@ -104,7 +104,7 @@ public class EvalMapperTests extends ESTestCase {
             new DateFormat(Source.EMPTY, literal, datePattern, TEST_CONFIG),
             new DateFormat(Source.EMPTY, literal, datePattern, TEST_CONFIG),
             new StartsWith(Source.EMPTY, literal, literal),
             new StartsWith(Source.EMPTY, literal, literal),
             new Substring(Source.EMPTY, literal, LONG, LONG),
             new Substring(Source.EMPTY, literal, LONG, LONG),
-            new DateTrunc(Source.EMPTY, DATE, dateInterval) }) {
+            new DateTrunc(Source.EMPTY, dateInterval, DATE) }) {
             params.add(new Object[] { e.nodeString(), e });
             params.add(new Object[] { e.nodeString(), e });
         }
         }