Browse Source

SQL: Wrap ZonedDateTime parameters inside scripts (#39911)

Painless allows ZonedDateTime objects to be passed natively to scripts
which creates problematic translate queries as the ZonedDateTime is
passed as a string instead.
Wrap this with a dedicated method to perform the conversion.

Fix #39877
Costin Leau 6 years ago
parent
commit
4957cad5bd

+ 5 - 2
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

@@ -348,7 +348,7 @@ public final class InternalSqlScriptUtils {
     public static ZonedDateTime asDateTime(Object dateTime) {
         return (ZonedDateTime) asDateTime(dateTime, false);
     }
-    
+
     private static Object asDateTime(Object dateTime, boolean lenient) {
         if (dateTime == null) {
             return null;
@@ -363,7 +363,10 @@ public final class InternalSqlScriptUtils {
             if (dateTime instanceof Number) {
                 return DateUtils.asDateTime(((Number) dateTime).longValue());
             }
-    
+
+            if (dateTime instanceof String) {
+                return DateUtils.asDateTime(dateTime.toString());
+            }
             throw new SqlIllegalArgumentException("Invalid date encountered [{}]", dateTime);
         }
         return dateTime;

+ 14 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/gen/script/ScriptWeaver.java

@@ -17,6 +17,9 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunctionAttr
 import org.elasticsearch.xpack.sql.expression.literal.IntervalDayTime;
 import org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth;
 import org.elasticsearch.xpack.sql.type.DataType;
+import org.elasticsearch.xpack.sql.util.DateUtils;
+
+import java.time.ZonedDateTime;
 
 import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
 
@@ -52,7 +55,18 @@ public interface ScriptWeaver {
 
     default ScriptTemplate scriptWithFoldable(Expression foldable) {
         Object fold = foldable.fold();
+
+        //
+        // Custom type handling
+        //
+
         // wrap intervals with dedicated methods for serialization
+        if (fold instanceof ZonedDateTime) {
+            ZonedDateTime zdt = (ZonedDateTime) fold;
+            return new ScriptTemplate(processScript("{sql}.asDateTime({})"),
+                    paramsBuilder().variable(DateUtils.toString(zdt)).build(), dataType());
+        }
+
         if (fold instanceof IntervalYearMonth) {
             IntervalYearMonth iym = (IntervalYearMonth) fold;
             return new ScriptTemplate(processScript("{sql}.intervalYearMonth({},{})"),

+ 14 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java

@@ -59,6 +59,7 @@ import java.util.stream.Stream;
 
 import static org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation.E;
 import static org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation.PI;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.Matchers.endsWith;
 import static org.hamcrest.Matchers.startsWith;
 
@@ -728,4 +729,17 @@ public class QueryTranslatorTests extends ESTestCase {
         EsQueryExec eqe = (EsQueryExec) p;
         assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
     }
+
+    public void testZonedDateTimeInScripts() throws Exception {
+        PhysicalPlan p = optimizeAndPlan(
+                "SELECT date FROM test WHERE date + INTERVAL 1 YEAR > CAST('2019-03-11T12:34:56.000Z' AS DATETIME)");
+        assertEquals(EsQueryExec.class, p.getClass());
+        EsQueryExec eqe = (EsQueryExec) p;
+        assertThat(eqe.queryContainer().toString().replaceAll("\\s+", ""), containsString(
+                "\"script\":{\"script\":{\"source\":\"InternalSqlScriptUtils.nullSafeFilter("
+                + "InternalSqlScriptUtils.gt(InternalSqlScriptUtils.add(InternalSqlScriptUtils.docValue(doc,params.v0),"
+                + "InternalSqlScriptUtils.intervalYearMonth(params.v1,params.v2)),InternalSqlScriptUtils.asDateTime(params.v3)))\","
+                + "\"lang\":\"painless\","
+                + "\"params\":{\"v0\":\"date\",\"v1\":\"P1Y\",\"v2\":\"INTERVAL_YEAR\",\"v3\":\"2019-03-11T12:34:56.000Z\"}},"));
+    }
 }