Przeglądaj źródła

SQL: Refactor binary date time functions (#47786)

Refactor DateTrunc and DatePart to use separate Pipe classes which
allows the removal of the BinaryDateOperation enum.
Marios Trivyzas 6 lat temu
rodzic
commit
a6075e7718
15 zmienionych plików z 375 dodań i 195 usunięć
  1. 7 9
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimeFunction.java
  2. 7 24
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimePipe.java
  3. 10 22
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimeProcessor.java
  4. 15 9
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePart.java
  5. 36 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartPipe.java
  6. 13 13
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartProcessor.java
  7. 29 25
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTrunc.java
  8. 36 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncPipe.java
  9. 13 13
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncProcessor.java
  10. 15 15
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java
  11. 131 0
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartPipeTests.java
  12. 3 4
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartProcessorTests.java
  13. 7 0
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeTestUtils.java
  14. 48 56
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncPipeTests.java
  15. 5 5
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncProcessorTests.java

+ 7 - 9
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimeFunction.java

@@ -20,19 +20,15 @@ import java.util.Objects;
 import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
 import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isDate;
 import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isString;
-import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.BinaryDateTimeProcessor.BinaryDateOperation;
 import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
 
 public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
 
     private final ZoneId zoneId;
-    private final BinaryDateOperation operation;
 
-    public BinaryDateTimeFunction(Source source, Expression datePart, Expression timestamp, ZoneId zoneId,
-                                  BinaryDateOperation operation) {
+    public BinaryDateTimeFunction(Source source, Expression datePart, Expression timestamp, ZoneId zoneId) {
         super(source, datePart, timestamp);
         this.zoneId = zoneId;
-        this.operation = operation;
     }
 
     @Override
@@ -47,7 +43,7 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
             if (datePartValue != null && resolveDateTimeField(datePartValue) == false) {
                 List<String> similar = findSimilarDateTimeFields(datePartValue);
                 if (similar.isEmpty()) {
-                    return new TypeResolution(format(null, "first argument of [{}] must be one of {} or their aliases, found value [{}]",
+                    return new TypeResolution(format(null, "first argument of [{}] must be one of {} or their aliases; found value [{}]",
                         sourceText(),
                         validDateTimeFieldValues(),
                         Expressions.name(left())));
@@ -78,9 +74,11 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
 
     @Override
     protected Pipe makePipe() {
-        return new BinaryDateTimePipe(source(), this, Expressions.pipe(left()), Expressions.pipe(right()), zoneId, operation);
+        return createPipe(Expressions.pipe(left()), Expressions.pipe(right()), zoneId);
     }
 
+    protected abstract Pipe createPipe(Pipe left, Pipe right, ZoneId zoneId);
+
     @Override
     public Nullability nullable() {
         return Nullability.TRUE;
@@ -101,7 +99,7 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), zoneId, operation);
+        return Objects.hash(super.hashCode(), zoneId);
     }
 
     @Override
@@ -116,6 +114,6 @@ public abstract class BinaryDateTimeFunction extends BinaryScalarFunction {
             return false;
         }
         BinaryDateTimeFunction that = (BinaryDateTimeFunction) o;
-        return zoneId.equals(that.zoneId) && operation == that.operation;
+        return zoneId.equals(that.zoneId);
     }
 }

+ 7 - 24
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimePipe.java

@@ -9,50 +9,34 @@ import org.elasticsearch.xpack.sql.expression.Expression;
 import org.elasticsearch.xpack.sql.expression.gen.pipeline.BinaryPipe;
 import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
 import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
-import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.tree.Source;
 
 import java.time.ZoneId;
 import java.util.Objects;
 
-public class BinaryDateTimePipe extends BinaryPipe {
+public abstract class BinaryDateTimePipe extends BinaryPipe {
 
     private final ZoneId zoneId;
-    private final BinaryDateTimeProcessor.BinaryDateOperation operation;
 
-    public BinaryDateTimePipe(Source source, Expression expression, Pipe left, Pipe right, ZoneId zoneId,
-                              BinaryDateTimeProcessor.BinaryDateOperation operation) {
+    public BinaryDateTimePipe(Source source, Expression expression, Pipe left, Pipe right, ZoneId zoneId) {
         super(source, expression, left, right);
         this.zoneId = zoneId;
-        this.operation = operation;
     }
 
     ZoneId zoneId() {
         return zoneId;
     }
 
-    BinaryDateTimeProcessor.BinaryDateOperation operation() {
-        return operation;
-    }
-
-    @Override
-    protected NodeInfo<BinaryDateTimePipe> info() {
-        return NodeInfo.create(this, BinaryDateTimePipe::new, expression(), left(), right(), zoneId, operation);
-    }
-
-    @Override
-    protected BinaryPipe replaceChildren(Pipe left, Pipe right) {
-        return new BinaryDateTimePipe(source(), expression(), left, right, zoneId, operation);
-    }
-
     @Override
     public Processor asProcessor() {
-        return BinaryDateTimeProcessor.asProcessor(operation, left().asProcessor(), right().asProcessor(), zoneId);
+        return makeProcessor(left().asProcessor(), right().asProcessor(), zoneId);
     }
 
+    protected abstract Processor makeProcessor(Processor left, Processor right, ZoneId zoneId);
+
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), zoneId, operation);
+        return Objects.hash(super.hashCode(), zoneId);
     }
 
     @Override
@@ -67,7 +51,6 @@ public class BinaryDateTimePipe extends BinaryPipe {
             return false;
         }
         BinaryDateTimePipe that = (BinaryDateTimePipe) o;
-        return Objects.equals(zoneId, that.zoneId) &&
-            operation == that.operation;
+        return Objects.equals(zoneId, that.zoneId);
     }
 }

+ 10 - 22
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimeProcessor.java

@@ -15,16 +15,8 @@ import java.io.IOException;
 import java.time.ZoneId;
 import java.util.Objects;
 
-import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.BinaryDateTimeProcessor.BinaryDateOperation.TRUNC;
-
 public abstract class BinaryDateTimeProcessor extends BinaryProcessor {
 
-    // TODO: Remove and in favour of inheritance (subclasses which implement abstract methods)
-    public enum BinaryDateOperation {
-        TRUNC,
-        PART;
-    }
-
     private final ZoneId zoneId;
 
     public BinaryDateTimeProcessor(Processor source1, Processor source2, ZoneId zoneId) {
@@ -48,28 +40,24 @@ public abstract class BinaryDateTimeProcessor extends BinaryProcessor {
     @Override
     protected abstract Object doProcess(Object left, Object right);
 
-    public static BinaryDateTimeProcessor asProcessor(BinaryDateOperation operation, Processor left, Processor right, ZoneId zoneId) {
-        if (operation == TRUNC) {
-            return new DateTruncProcessor(left, right, zoneId);
-        } else {
-            return new DatePartProcessor(left, right, zoneId);
-        }
-    }
-
     @Override
     public int hashCode() {
-        return Objects.hash(zoneId);
+        return Objects.hash(left(), right(), zoneId);
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o) {
+    public boolean equals(Object obj) {
+        if (this == obj) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+
+        if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        BinaryDateTimeProcessor that = (BinaryDateTimeProcessor) o;
-        return zoneId.equals(that.zoneId);
+
+        BinaryDateTimeProcessor other = (BinaryDateTimeProcessor) obj;
+        return Objects.equals(left(), other.left())
+            && Objects.equals(right(), other.right())
+            && Objects.equals(zoneId(), other.zoneId());
     }
 }

+ 15 - 9
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePart.java

@@ -9,6 +9,7 @@ import org.elasticsearch.xpack.sql.expression.Expression;
 import org.elasticsearch.xpack.sql.expression.Nullability;
 import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction;
 import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
 import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.tree.Source;
 import org.elasticsearch.xpack.sql.type.DataType;
@@ -76,7 +77,7 @@ public class DatePart extends BinaryDateTimeFunction {
     }
 
     public DatePart(Source source, Expression truncateTo, Expression timestamp, ZoneId zoneId) {
-        super(source, truncateTo, timestamp, zoneId, BinaryDateTimeProcessor.BinaryDateOperation.PART);
+        super(source, truncateTo, timestamp, zoneId);
     }
 
     @Override
@@ -100,23 +101,28 @@ public class DatePart extends BinaryDateTimeFunction {
     }
 
     @Override
-    protected boolean resolveDateTimeField(String dateTimeField) {
-        return Part.resolve(dateTimeField) != null;
+    protected String scriptMethodName() {
+        return "datePart";
     }
 
     @Override
-    protected List<String> findSimilarDateTimeFields(String dateTimeField) {
-        return Part.findSimilar(dateTimeField);
+    public Object fold() {
+        return DatePartProcessor.process(left().fold(), right().fold(), zoneId());
     }
 
     @Override
-    protected String scriptMethodName() {
-        return "datePart";
+    protected Pipe createPipe(Pipe left, Pipe right, ZoneId zoneId) {
+        return new DatePartPipe(source(), this, left, right, zoneId);
     }
 
     @Override
-    public Object fold() {
-        return DatePartProcessor.process(left().fold(), right().fold(), zoneId());
+    protected boolean resolveDateTimeField(String dateTimeField) {
+        return Part.resolve(dateTimeField) != null;
+    }
+
+    @Override
+    protected List<String> findSimilarDateTimeFields(String dateTimeField) {
+        return Part.findSimilar(dateTimeField);
     }
 
     @Override

+ 36 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartPipe.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
+
+import org.elasticsearch.xpack.sql.expression.Expression;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+import org.elasticsearch.xpack.sql.tree.Source;
+
+import java.time.ZoneId;
+
+public class DatePartPipe extends BinaryDateTimePipe {
+
+    public DatePartPipe(Source source, Expression expression, Pipe left, Pipe right, ZoneId zoneId) {
+        super(source, expression, left, right, zoneId);
+    }
+
+    @Override
+    protected NodeInfo<DatePartPipe> info() {
+        return NodeInfo.create(this, DatePartPipe::new, expression(), left(), right(), zoneId());
+    }
+
+    @Override
+    protected DatePartPipe replaceChildren(Pipe left, Pipe right) {
+        return new DatePartPipe(source(), expression(), left, right, zoneId());
+    }
+
+    @Override
+    protected Processor makeProcessor(Processor left, Processor right, ZoneId zoneId) {
+        return new DatePartProcessor(left, right, zoneId);
+    }
+}

+ 13 - 13
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartProcessor.java

@@ -34,36 +34,36 @@ public class DatePartProcessor extends BinaryDateTimeProcessor {
     }
 
     @Override
-    protected Object doProcess(Object left, Object right) {
-        return process(left, right, zoneId());
+    protected Object doProcess(Object part, Object timestamp) {
+        return process(part, timestamp, zoneId());
     }
 
     /**
      * Used in Painless scripting
      */
-    public static Object process(Object source1, Object source2, ZoneId zoneId) {
-        if (source1 == null || source2 == null) {
+    public static Object process(Object part, Object timestamp, ZoneId zoneId) {
+        if (part == null || timestamp == null) {
             return null;
         }
-        if (source1 instanceof String == false) {
-            throw new SqlIllegalArgumentException("A string is required; received [{}]", source1);
+        if (part instanceof String == false) {
+            throw new SqlIllegalArgumentException("A string is required; received [{}]", part);
         }
-        Part datePartField = Part.resolve((String) source1);
+        Part datePartField = Part.resolve((String) part);
         if (datePartField == null) {
-            List<String> similar = Part.findSimilar((String) source1);
+            List<String> similar = Part.findSimilar((String) part);
             if (similar.isEmpty()) {
                 throw new SqlIllegalArgumentException("A value of {} or their aliases is required; received [{}]",
-                    Part.values(), source1);
+                    Part.values(), part);
             } else {
                 throw new SqlIllegalArgumentException("Received value [{}] is not valid date part for extraction; " +
-                    "did you mean {}?", source1, similar);
+                    "did you mean {}?", part, similar);
             }
         }
 
-        if (source2 instanceof ZonedDateTime == false) {
-            throw new SqlIllegalArgumentException("A date/datetime is required; received [{}]", source2);
+        if (timestamp instanceof ZonedDateTime == false) {
+            throw new SqlIllegalArgumentException("A date/datetime is required; received [{}]", timestamp);
         }
 
-        return datePartField.extract(((ZonedDateTime) source2).withZoneSameInstant(zoneId));
+        return datePartField.extract(((ZonedDateTime) timestamp).withZoneSameInstant(zoneId));
     }
 }

+ 29 - 25
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTrunc.java

@@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
 import org.elasticsearch.xpack.sql.expression.Expression;
 import org.elasticsearch.xpack.sql.expression.Nullability;
 import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
 import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.tree.Source;
 import org.elasticsearch.xpack.sql.type.DataType;
@@ -18,9 +19,7 @@ import java.time.temporal.ChronoField;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
-
-import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.BinaryDateTimeProcessor.BinaryDateOperation.TRUNC;
+import java.util.function.UnaryOperator;
 
 public class DateTrunc extends BinaryDateTimeFunction {
 
@@ -34,7 +33,7 @@ public class DateTrunc extends BinaryDateTimeFunction {
                 .with(ChronoField.MONTH_OF_YEAR, 1)
                 .with(ChronoField.DAY_OF_MONTH, 1)
                 .toLocalDate().atStartOfDay(dt.getZone());
-            },"millennia"),
+        },"millennia"),
         CENTURY(dt -> {
             int year = dt.getYear();
             int firstYearOfCentury = year - (year % 100);
@@ -43,7 +42,7 @@ public class DateTrunc extends BinaryDateTimeFunction {
                 .with(ChronoField.MONTH_OF_YEAR, 1)
                 .with(ChronoField.DAY_OF_MONTH, 1)
                 .toLocalDate().atStartOfDay(dt.getZone());
-            }, "centuries"),
+        }, "centuries"),
         DECADE(dt -> {
             int year = dt.getYear();
             int firstYearOfDecade = year - (year % 10);
@@ -52,7 +51,7 @@ public class DateTrunc extends BinaryDateTimeFunction {
                 .with(ChronoField.MONTH_OF_YEAR, 1)
                 .with(ChronoField.DAY_OF_MONTH, 1)
                 .toLocalDate().atStartOfDay(dt.getZone());
-            }, "decades"),
+        }, "decades"),
         YEAR(dt -> dt
             .with(ChronoField.MONTH_OF_YEAR, 1)
             .with(ChronoField.DAY_OF_MONTH, 1)
@@ -65,14 +64,14 @@ public class DateTrunc extends BinaryDateTimeFunction {
                 .with(ChronoField.MONTH_OF_YEAR, firstMonthOfQuarter)
                 .with(ChronoField.DAY_OF_MONTH, 1)
                 .toLocalDate().atStartOfDay(dt.getZone());
-            }, "quarters", "qq", "q"),
+        }, "quarters", "qq", "q"),
         MONTH(dt -> dt
-                .with(ChronoField.DAY_OF_MONTH, 1)
-                .toLocalDate().atStartOfDay(dt.getZone()),
+            .with(ChronoField.DAY_OF_MONTH, 1)
+            .toLocalDate().atStartOfDay(dt.getZone()),
             "months", "mm", "m"),
         WEEK(dt -> dt
-                .with(ChronoField.DAY_OF_WEEK, 1)
-                .toLocalDate().atStartOfDay(dt.getZone()),
+            .with(ChronoField.DAY_OF_WEEK, 1)
+            .toLocalDate().atStartOfDay(dt.getZone()),
             "weeks", "wk", "ww"),
         DAY(dt -> dt.toLocalDate().atStartOfDay(dt.getZone()), "days", "dd", "d"),
         HOUR(dt -> {
@@ -86,16 +85,16 @@ public class DateTrunc extends BinaryDateTimeFunction {
             return dt.toLocalDate().atStartOfDay(dt.getZone())
                 .with(ChronoField.HOUR_OF_DAY, hour)
                 .with(ChronoField.MINUTE_OF_HOUR, minute);
-            }, "minutes", "mi", "n"),
+        }, "minutes", "mi", "n"),
         SECOND(dt -> dt.with(ChronoField.NANO_OF_SECOND, 0), "seconds", "ss", "s"),
         MILLISECOND(dt -> {
             int micros = dt.get(ChronoField.MICRO_OF_SECOND);
             return dt.with(ChronoField.MILLI_OF_SECOND, (micros / 1000));
-            }, "milliseconds", "ms"),
+        }, "milliseconds", "ms"),
         MICROSECOND(dt -> {
             int nanos = dt.getNano();
             return dt.with(ChronoField.MICRO_OF_SECOND, (nanos / 1000));
-            }, "microseconds", "mcs"),
+        }, "microseconds", "mcs"),
         NANOSECOND(dt -> dt, "nanoseconds", "ns");
 
         private static final Map<String, Part> NAME_TO_PART;
@@ -106,10 +105,10 @@ public class DateTrunc extends BinaryDateTimeFunction {
             VALID_VALUES = DateTimeField.initializeValidValues(values());
         }
 
-        private Function<ZonedDateTime, ZonedDateTime> truncateFunction;
+        private UnaryOperator<ZonedDateTime> truncateFunction;
         private Set<String> aliases;
 
-        Part(Function<ZonedDateTime, ZonedDateTime> truncateFunction, String... aliases) {
+        Part(UnaryOperator<ZonedDateTime> truncateFunction, String... aliases) {
             this.truncateFunction = truncateFunction;
             this.aliases = Set.of(aliases);
         }
@@ -133,7 +132,7 @@ public class DateTrunc extends BinaryDateTimeFunction {
     }
 
     public DateTrunc(Source source, Expression truncateTo, Expression timestamp, ZoneId zoneId) {
-        super(source, truncateTo, timestamp, zoneId, TRUNC);
+        super(source, truncateTo, timestamp, zoneId);
     }
 
     @Override
@@ -157,23 +156,28 @@ public class DateTrunc extends BinaryDateTimeFunction {
     }
 
     @Override
-    protected boolean resolveDateTimeField(String dateTimeField) {
-        return Part.resolve(dateTimeField) != null;
+    protected String scriptMethodName() {
+        return "dateTrunc";
     }
 
     @Override
-    protected List<String> findSimilarDateTimeFields(String dateTimeField) {
-        return Part.findSimilar(dateTimeField);
+    public Object fold() {
+        return DateTruncProcessor.process(left().fold(), right().fold(), zoneId());
     }
 
     @Override
-    protected String scriptMethodName() {
-        return "dateTrunc";
+    protected Pipe createPipe(Pipe left, Pipe right, ZoneId zoneId) {
+        return new DateTruncPipe(source(), this, left, right, zoneId);
     }
 
     @Override
-    public Object fold() {
-        return DateTruncProcessor.process(left().fold(), right().fold(), zoneId());
+    protected boolean resolveDateTimeField(String dateTimeField) {
+        return Part.resolve(dateTimeField) != null;
+    }
+
+    @Override
+    protected List<String> findSimilarDateTimeFields(String dateTimeField) {
+        return Part.findSimilar(dateTimeField);
     }
 
     @Override

+ 36 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncPipe.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
+
+import org.elasticsearch.xpack.sql.expression.Expression;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+import org.elasticsearch.xpack.sql.tree.Source;
+
+import java.time.ZoneId;
+
+public class DateTruncPipe extends BinaryDateTimePipe {
+
+    public DateTruncPipe(Source source, Expression expression, Pipe left, Pipe right, ZoneId zoneId) {
+        super(source, expression, left, right, zoneId);
+    }
+
+    @Override
+    protected NodeInfo<DateTruncPipe> info() {
+        return NodeInfo.create(this, DateTruncPipe::new, expression(), left(), right(), zoneId());
+    }
+
+    @Override
+    protected DateTruncPipe replaceChildren(Pipe left, Pipe right) {
+        return new DateTruncPipe(source(), expression(), left, right, zoneId());
+    }
+
+    @Override
+    protected Processor makeProcessor(Processor left, Processor right, ZoneId zoneId) {
+        return new DateTruncProcessor(left, right, zoneId);
+    }
+}

+ 13 - 13
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncProcessor.java

@@ -34,36 +34,36 @@ public class DateTruncProcessor extends BinaryDateTimeProcessor {
     }
 
     @Override
-    protected Object doProcess(Object left, Object right) {
-        return process(left, right, zoneId());
+    protected Object doProcess(Object truncateTo, Object timestamp) {
+        return process(truncateTo, timestamp, zoneId());
     }
 
     /**
      * Used in Painless scripting
      */
-    public static Object process(Object source1, Object source2, ZoneId zoneId) {
-        if (source1 == null || source2 == null) {
+    public static Object process(Object truncateTo, Object timestamp, ZoneId zoneId) {
+        if (truncateTo == null || timestamp == null) {
             return null;
         }
-        if (source1 instanceof String == false) {
-            throw new SqlIllegalArgumentException("A string is required; received [{}]", source1);
+        if (truncateTo instanceof String == false) {
+            throw new SqlIllegalArgumentException("A string is required; received [{}]", truncateTo);
         }
-        Part truncateDateField = Part.resolve((String) source1);
+        Part truncateDateField = Part.resolve((String) truncateTo);
         if (truncateDateField == null) {
-            List<String> similar = Part.findSimilar((String) source1);
+            List<String> similar = Part.findSimilar((String) truncateTo);
             if (similar.isEmpty()) {
                 throw new SqlIllegalArgumentException("A value of {} or their aliases is required; received [{}]",
-                    Part.values(), source1);
+                    Part.values(), truncateTo);
             } else {
                 throw new SqlIllegalArgumentException("Received value [{}] is not valid date part for truncation; " +
-                    "did you mean {}?", source1, similar);
+                    "did you mean {}?", truncateTo, similar);
             }
         }
 
-        if (source2 instanceof ZonedDateTime == false) {
-            throw new SqlIllegalArgumentException("A date/datetime is required; received [{}]", source2);
+        if (timestamp instanceof ZonedDateTime == false) {
+            throw new SqlIllegalArgumentException("A date/datetime is required; received [{}]", timestamp);
         }
 
-        return truncateDateField.truncate(((ZonedDateTime) source2).withZoneSameInstant(zoneId));
+        return truncateDateField.truncate(((ZonedDateTime) timestamp).withZoneSameInstant(zoneId));
     }
 }

+ 15 - 15
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

@@ -80,7 +80,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
     private LogicalPlan incompatibleAccept(String sql) {
         return accept(incompatible(), sql);
     }
-    
+
     public void testMissingIndex() {
         assertEquals("1:17: Unknown index [missing]", error(IndexResolution.notFound("missing"), "SELECT foo FROM missing"));
     }
@@ -96,11 +96,11 @@ public class VerifierErrorMessagesTests extends ESTestCase {
     public void testMissingColumnWithWildcard() {
         assertEquals("1:8: Unknown column [xxx]", error("SELECT xxx.* FROM test"));
     }
-    
+
     public void testMisspelledColumnWithWildcard() {
         assertEquals("1:8: Unknown column [tex], did you mean [text]?", error("SELECT tex.* FROM test"));
     }
-    
+
     public void testColumnWithNoSubFields() {
         assertEquals("1:8: Cannot determine columns for [text.*]", error("SELECT text.* FROM test"));
     }
@@ -131,14 +131,14 @@ public class VerifierErrorMessagesTests extends ESTestCase {
                 "line 1:22: Unknown column [c]\n" +
                 "line 1:25: Unknown column [tex], did you mean [text]?", error("SELECT bool, a, b.*, c, tex.* FROM test"));
     }
-    
+
     public void testMultipleColumnsWithWildcard2() {
         assertEquals("1:8: Unknown column [tex], did you mean [text]?\n" +
                 "line 1:21: Unknown column [a]\n" +
                 "line 1:24: Unknown column [dat], did you mean [date]?\n" +
                 "line 1:31: Unknown column [c]", error("SELECT tex.*, bool, a, dat.*, c FROM test"));
     }
-    
+
     public void testMultipleColumnsWithWildcard3() {
         assertEquals("1:8: Unknown column [ate], did you mean [date]?\n" +
                 "line 1:21: Unknown column [keyw], did you mean [keyword]?\n" +
@@ -210,7 +210,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
                 "type [keyword]", error("SELECT DATE_TRUNC(keyword, keyword) FROM test"));
         assertEquals("1:8: first argument of [DATE_TRUNC('invalid', keyword)] must be one of [MILLENNIUM, CENTURY, DECADE, " + "" +
                 "YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND] " +
-                "or their aliases, found value ['invalid']",
+                "or their aliases; found value ['invalid']",
             error("SELECT DATE_TRUNC('invalid', keyword) FROM test"));
         assertEquals("1:8: Unknown value ['millenioum'] for first argument of [DATE_TRUNC('millenioum', keyword)]; " +
                 "did you mean [millennium, millennia]?",
@@ -237,7 +237,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
             "type [keyword]", error("SELECT DATE_PART(keyword, keyword) FROM test"));
         assertEquals("1:8: first argument of [DATE_PART('invalid', keyword)] must be one of [YEAR, QUARTER, MONTH, DAYOFYEAR, " +
             "DAY, WEEK, WEEKDAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, TZOFFSET] " +
-                "or their aliases, found value ['invalid']",
+                "or their aliases; found value ['invalid']",
             error("SELECT DATE_PART('invalid', keyword) FROM test"));
         assertEquals("1:8: Unknown value ['tzofset'] for first argument of [DATE_PART('tzofset', keyword)]; " +
                 "did you mean [tzoffset]?",
@@ -616,13 +616,13 @@ public class VerifierErrorMessagesTests extends ESTestCase {
                 "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
             error("SELECT * FROM test WHERE text LIKE 'foo'"));
     }
-    
+
     public void testInvalidTypeForRLikeMatch() {
         assertEquals("1:26: [text RLIKE 'foo'] cannot operate on field of data type [text]: " +
                 "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
             error("SELECT * FROM test WHERE text RLIKE 'foo'"));
     }
-    
+
     public void testAllowCorrectFieldsInIncompatibleMappings() {
         assertNotNull(incompatibleAccept("SELECT languages FROM \"*\""));
     }
@@ -746,32 +746,32 @@ public class VerifierErrorMessagesTests extends ESTestCase {
         assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
     }
-    
+
     public void testHistogramNotInGroupingWithCount() {
         assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, COUNT(*) FROM test"));
     }
-    
+
     public void testHistogramNotInGroupingWithMaxFirst() {
         assertEquals("1:19: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT MAX(date), HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
     }
-    
+
     public void testHistogramWithoutAliasNotInGrouping() {
         assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test"));
     }
-    
+
     public void testTwoHistogramsNotInGrouping() {
         assertEquals("1:48: [HISTOGRAM(date, INTERVAL 1 DAY)] needs to be part of the grouping",
                 error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, HISTOGRAM(date, INTERVAL 1 DAY) FROM test GROUP BY h"));
     }
-    
+
     public void testHistogramNotInGrouping_WithGroupByField() {
         assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test GROUP BY date"));
     }
-    
+
     public void testScalarOfHistogramNotInGrouping() {
         assertEquals("1:14: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                 error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) FROM test"));

+ 131 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartPipeTests.java

@@ -0,0 +1,131 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.sql.expression.Expression;
+import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.BinaryPipe;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
+import org.elasticsearch.xpack.sql.tree.Source;
+import org.elasticsearch.xpack.sql.tree.SourceTests;
+
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import static org.elasticsearch.xpack.sql.expression.Expressions.pipe;
+import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomDatetimeLiteral;
+import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
+import static org.elasticsearch.xpack.sql.tree.SourceTests.randomSource;
+
+public class DatePartPipeTests extends AbstractNodeTestCase<DatePartPipe, Pipe> {
+
+    @Override
+    protected DatePartPipe randomInstance() {
+        return randomDatePartPipe();
+    }
+
+    private Expression randomDatePartPipeExpression() {
+        return randomDatePartPipe().expression();
+    }
+
+    public static DatePartPipe randomDatePartPipe() {
+        return (DatePartPipe) new DatePart(
+            randomSource(),
+            randomStringLiteral(),
+            randomDatetimeLiteral(),
+            randomZone())
+            .makePipe();
+    }
+
+    @Override
+    public void testTransform() {
+        // test transforming only the properties (source, expression),
+        // skipping the children (the two parameters of the binary function) which are tested separately
+        DatePartPipe b1 = randomInstance();
+
+        Expression newExpression = randomValueOtherThan(b1.expression(), this::randomDatePartPipeExpression);
+        DatePartPipe newB = new DatePartPipe(
+            b1.source(),
+            newExpression,
+            b1.left(),
+            b1.right(),
+            b1.zoneId());
+        assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
+
+        DatePartPipe b2 = randomInstance();
+        Source newLoc = randomValueOtherThan(b2.source(), SourceTests::randomSource);
+        newB = new DatePartPipe(
+            newLoc,
+            b2.expression(),
+            b2.left(),
+            b2.right(),
+            b2.zoneId());
+        assertEquals(newB,
+            b2.transformPropertiesOnly(v -> Objects.equals(v, b2.source()) ? newLoc : v, Source.class));
+    }
+
+    @Override
+    public void testReplaceChildren() {
+        DatePartPipe b = randomInstance();
+        Pipe newLeft = pipe(((Expression) randomValueOtherThan(b.left(), FunctionTestUtils::randomStringLiteral)));
+        Pipe newRight = pipe(((Expression) randomValueOtherThan(b.right(), FunctionTestUtils::randomDatetimeLiteral)));
+        ZoneId newZoneId = randomValueOtherThan(b.zoneId(), ESTestCase::randomZone);
+        DatePartPipe newB = new DatePartPipe( b.source(), b.expression(), b.left(), b.right(), newZoneId);
+        BinaryPipe transformed = newB.replaceChildren(newLeft, b.right());
+
+        assertEquals(transformed.left(), newLeft);
+        assertEquals(transformed.source(), b.source());
+        assertEquals(transformed.expression(), b.expression());
+        assertEquals(transformed.right(), b.right());
+
+        transformed = newB.replaceChildren(b.left(), newRight);
+        assertEquals(transformed.left(), b.left());
+        assertEquals(transformed.source(), b.source());
+        assertEquals(transformed.expression(), b.expression());
+        assertEquals(transformed.right(), newRight);
+
+        transformed = newB.replaceChildren(newLeft, newRight);
+        assertEquals(transformed.left(), newLeft);
+        assertEquals(transformed.source(), b.source());
+        assertEquals(transformed.expression(), b.expression());
+        assertEquals(transformed.right(), newRight);
+    }
+
+    @Override
+    protected DatePartPipe mutate(DatePartPipe instance) {
+        List<Function<DatePartPipe, DatePartPipe>> randoms = new ArrayList<>();
+        randoms.add(f -> new DatePartPipe(f.source(), f.expression(),
+            pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
+            f.right(),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
+        randoms.add(f -> new DatePartPipe(f.source(), f.expression(),
+            f.left(),
+            pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
+        randoms.add(f -> new DatePartPipe(f.source(), f.expression(),
+            pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
+            pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
+
+        return randomFrom(randoms).apply(instance);
+    }
+
+    @Override
+    protected DatePartPipe copy(DatePartPipe instance) {
+        return new DatePartPipe(
+            instance.source(),
+            instance.expression(),
+            instance.left(),
+            instance.right(),
+            instance.zoneId());
+    }
+}

+ 3 - 4
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DatePartProcessorTests.java

@@ -15,7 +15,6 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
 import org.elasticsearch.xpack.sql.tree.Source;
 
 import java.time.ZoneId;
-import java.time.ZonedDateTime;
 
 import static org.elasticsearch.xpack.sql.expression.Literal.NULL;
 import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
@@ -27,7 +26,7 @@ public class DatePartProcessorTests extends AbstractSqlWireSerializingTestCase<D
     public static DatePartProcessor randomDatePartProcessor() {
         return new DatePartProcessor(
             new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
-            new ConstantProcessor(ZonedDateTime.now()),
+            new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
             randomZone());
     }
 
@@ -50,7 +49,7 @@ public class DatePartProcessorTests extends AbstractSqlWireSerializingTestCase<D
     protected DatePartProcessor mutateInstance(DatePartProcessor instance) {
         return new DatePartProcessor(
             new ConstantProcessor(ESTestCase.randomRealisticUnicodeOfLength(128)),
-            new ConstantProcessor(ZonedDateTime.now()),
+            new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
             randomValueOtherThan(instance.zoneId(), ESTestCase::randomZone));
     }
 
@@ -72,7 +71,7 @@ public class DatePartProcessorTests extends AbstractSqlWireSerializingTestCase<D
         siae = expectThrows(SqlIllegalArgumentException.class,
             () -> new DatePart(Source.EMPTY, l("dayfyear"), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
         assertEquals("Received value [dayfyear] is not valid date part for extraction; did you mean [dayofyear, year]?",
-             siae.getMessage());
+            siae.getMessage());
     }
 
     public void testWithNulls() {

+ 7 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeTestUtils.java

@@ -8,6 +8,8 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
 
 import org.elasticsearch.xpack.sql.util.DateUtils;
 
+import java.time.Clock;
+import java.time.Duration;
 import java.time.OffsetTime;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
@@ -39,4 +41,9 @@ public class DateTimeTestUtils {
     public static OffsetTime time(int hour, int minute, int second, int nano) {
         return OffsetTime.of(hour, minute, second, nano, ZoneOffset.UTC);
     }
+
+    static ZonedDateTime nowWithMillisResolution() {
+        Clock millisResolutionClock = Clock.tick(Clock.systemUTC(), Duration.ofMillis(1));
+        return ZonedDateTime.now(millisResolutionClock);
+    }
 }

+ 48 - 56
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/BinaryDateTimePipeTests.java → x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncPipeTests.java

@@ -22,13 +22,14 @@ import java.util.Objects;
 import java.util.function.Function;
 
 import static org.elasticsearch.xpack.sql.expression.Expressions.pipe;
+import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomDatetimeLiteral;
 import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
 import static org.elasticsearch.xpack.sql.tree.SourceTests.randomSource;
 
-public class BinaryDateTimePipeTests extends AbstractNodeTestCase<BinaryDateTimePipe, Pipe> {
+public class DateTruncPipeTests extends AbstractNodeTestCase<DateTruncPipe, Pipe> {
 
     @Override
-    protected BinaryDateTimePipe randomInstance() {
+    protected DateTruncPipe randomInstance() {
         return randomDateTruncPipe();
     }
 
@@ -36,52 +37,49 @@ public class BinaryDateTimePipeTests extends AbstractNodeTestCase<BinaryDateTime
         return randomDateTruncPipe().expression();
     }
 
-    public static BinaryDateTimePipe randomDateTruncPipe() {
-        return (BinaryDateTimePipe) new DateTrunc(
-                randomSource(),
-                randomStringLiteral(),
-                randomStringLiteral(),
-                randomZone())
-                .makePipe();
+    public static DateTruncPipe randomDateTruncPipe() {
+        return (DateTruncPipe) new DateTrunc(
+            randomSource(),
+            randomStringLiteral(),
+            randomDatetimeLiteral(),
+            randomZone())
+            .makePipe();
     }
 
     @Override
     public void testTransform() {
         // test transforming only the properties (source, expression),
         // skipping the children (the two parameters of the binary function) which are tested separately
-        BinaryDateTimePipe b1 = randomInstance();
+        DateTruncPipe b1 = randomInstance();
 
         Expression newExpression = randomValueOtherThan(b1.expression(), this::randomDateTruncPipeExpression);
-        BinaryDateTimePipe newB = new BinaryDateTimePipe(
-                b1.source(),
-                newExpression,
-                b1.left(),
-                b1.right(),
-                b1.zoneId(),
-                b1.operation());
+        DateTruncPipe newB = new DateTruncPipe(
+            b1.source(),
+            newExpression,
+            b1.left(),
+            b1.right(),
+            b1.zoneId());
         assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
 
-        BinaryDateTimePipe b2 = randomInstance();
+        DateTruncPipe b2 = randomInstance();
         Source newLoc = randomValueOtherThan(b2.source(), SourceTests::randomSource);
-        newB = new BinaryDateTimePipe(
-                newLoc,
-                b2.expression(),
-                b2.left(),
-                b2.right(),
-                b2.zoneId(),
-                b2.operation());
+        newB = new DateTruncPipe(
+            newLoc,
+            b2.expression(),
+            b2.left(),
+            b2.right(),
+            b2.zoneId());
         assertEquals(newB,
-                b2.transformPropertiesOnly(v -> Objects.equals(v, b2.source()) ? newLoc : v, Source.class));
+            b2.transformPropertiesOnly(v -> Objects.equals(v, b2.source()) ? newLoc : v, Source.class));
     }
 
     @Override
     public void testReplaceChildren() {
-        BinaryDateTimePipe b = randomInstance();
+        DateTruncPipe b = randomInstance();
         Pipe newLeft = pipe(((Expression) randomValueOtherThan(b.left(), FunctionTestUtils::randomStringLiteral)));
         Pipe newRight = pipe(((Expression) randomValueOtherThan(b.right(), FunctionTestUtils::randomDatetimeLiteral)));
         ZoneId newZoneId = randomValueOtherThan(b.zoneId(), ESTestCase::randomZone);
-        BinaryDateTimePipe newB = new BinaryDateTimePipe(
-            b.source(), b.expression(), b.left(), b.right(), newZoneId, randomFrom(BinaryDateTimeProcessor.BinaryDateOperation.values()));
+        DateTruncPipe newB = new DateTruncPipe( b.source(), b.expression(), b.left(), b.right(), newZoneId);
         BinaryPipe transformed = newB.replaceChildren(newLeft, b.right());
 
         assertEquals(transformed.left(), newLeft);
@@ -103,37 +101,31 @@ public class BinaryDateTimePipeTests extends AbstractNodeTestCase<BinaryDateTime
     }
 
     @Override
-    protected BinaryDateTimePipe mutate(BinaryDateTimePipe instance) {
-        List<Function<BinaryDateTimePipe, BinaryDateTimePipe>> randoms = new ArrayList<>();
-        randoms.add(f -> new BinaryDateTimePipe(f.source(),
-                f.expression(),
-                pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
-                f.right(),
-                randomValueOtherThan(f.zoneId(), ESTestCase::randomZone),
-                randomFrom(BinaryDateTimeProcessor.BinaryDateOperation.values())));
-        randoms.add(f -> new BinaryDateTimePipe(f.source(),
-                f.expression(),
-                f.left(),
-                pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
-                randomValueOtherThan(f.zoneId(), ESTestCase::randomZone),
-                randomFrom(BinaryDateTimeProcessor.BinaryDateOperation.values())));
-        randoms.add(f -> new BinaryDateTimePipe(f.source(),
-                f.expression(),
-                pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
-                pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
-                randomValueOtherThan(f.zoneId(), ESTestCase::randomZone),
-                randomFrom(BinaryDateTimeProcessor.BinaryDateOperation.values())));
+    protected DateTruncPipe mutate(DateTruncPipe instance) {
+        List<Function<DateTruncPipe, DateTruncPipe>> randoms = new ArrayList<>();
+        randoms.add(f -> new DateTruncPipe(f.source(), f.expression(),
+            pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
+            f.right(),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
+        randoms.add(f -> new DateTruncPipe(f.source(), f.expression(),
+            f.left(),
+            pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
+        randoms.add(f -> new DateTruncPipe(f.source(), f.expression(),
+            pipe(((Expression) randomValueOtherThan(f.left(), FunctionTestUtils::randomStringLiteral))),
+            pipe(((Expression) randomValueOtherThan(f.right(), FunctionTestUtils::randomDatetimeLiteral))),
+            randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
 
         return randomFrom(randoms).apply(instance);
     }
 
     @Override
-    protected BinaryDateTimePipe copy(BinaryDateTimePipe instance) {
-        return new BinaryDateTimePipe(instance.source(),
-                instance.expression(),
-                instance.left(),
-                instance.right(),
-                instance.zoneId(),
-                instance.operation());
+    protected DateTruncPipe copy(DateTruncPipe instance) {
+        return new DateTruncPipe(
+            instance.source(),
+            instance.expression(),
+            instance.left(),
+            instance.right(),
+            instance.zoneId());
     }
 }

+ 5 - 5
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTruncProcessorTests.java

@@ -29,7 +29,7 @@ public class DateTruncProcessorTests extends AbstractSqlWireSerializingTestCase<
     public static DateTruncProcessor randomDateTruncProcessor() {
         return new DateTruncProcessor(
             new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
-            new ConstantProcessor(ZonedDateTime.now()),
+            new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
             randomZone());
     }
 
@@ -52,13 +52,13 @@ public class DateTruncProcessorTests extends AbstractSqlWireSerializingTestCase<
     protected DateTruncProcessor mutateInstance(DateTruncProcessor instance) {
         return new DateTruncProcessor(
             new ConstantProcessor(ESTestCase.randomRealisticUnicodeOfLength(128)),
-            new ConstantProcessor(ZonedDateTime.now()),
+            new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
             randomValueOtherThan(instance.zoneId(), ESTestCase::randomZone));
     }
 
     public void testInvalidInputs() {
         SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
-                () -> new DateTrunc(Source.EMPTY, l(5), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
+            () -> new DateTrunc(Source.EMPTY, l(5), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
         assertEquals("A string is required; received [5]", siae.getMessage());
 
         siae = expectThrows(SqlIllegalArgumentException.class,
@@ -68,13 +68,13 @@ public class DateTruncProcessorTests extends AbstractSqlWireSerializingTestCase<
         siae = expectThrows(SqlIllegalArgumentException.class,
             () -> new DateTrunc(Source.EMPTY, l("invalid"), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
         assertEquals("A value of [MILLENNIUM, CENTURY, DECADE, YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, " +
-            "SECOND, MILLISECOND, MICROSECOND, NANOSECOND] or their aliases is required; received [invalid]",
+                "SECOND, MILLISECOND, MICROSECOND, NANOSECOND] or their aliases is required; received [invalid]",
             siae.getMessage());
 
         siae = expectThrows(SqlIllegalArgumentException.class,
             () -> new DateTrunc(Source.EMPTY, l("dacede"), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
         assertEquals("Received value [dacede] is not valid date part for truncation; did you mean [decade, decades]?",
-             siae.getMessage());
+            siae.getMessage());
     }
 
     public void testWithNulls() {