Browse Source

Add several context examples for Painless date documentation (#44985)

Jack Conradson 6 years ago
parent
commit
796dec34c8

+ 190 - 5
docs/painless/painless-guide/painless-datetime.asciidoc

@@ -541,7 +541,7 @@ document is most commonly accessible through an input called `doc`.
 +
 [source,Painless]
 ----
-def input = doc['input_datetime'].value;
+ZonedDateTime input = doc['input_datetime'].value;
 String output = input.format(DateTimeFormatter.ISO_INSTANT); <1>
 ----
 <1> Note the use of a built-in DateTimeFormatter.
@@ -584,8 +584,8 @@ if (doc.containsKey('start') && doc.containsKey('end')) { <1>
 
     if (doc['start'].size() > 0 && doc['end'].size() > 0) { <2>
 
-        def start = doc['start'].value;
-        def end = doc['end'].value;
+        ZonedDateTime start = doc['start'].value;
+        ZonedDateTime end = doc['end'].value;
         long differenceInMillis = ChronoUnit.MILLIS.between(start, end);
 
         // handle difference in times
@@ -660,7 +660,7 @@ preferred as there is no need to parse it for comparision.
 [source,Painless]
 ----
 long now = params['now'];
-def inputDateTime = doc['input_datetime'];
+ZonedDateTime inputDateTime = doc['input_datetime'];
 long millisDateTime = zdt.toInstant().toEpochMilli();
 long elapsedTime = now - millisDateTime;
 ----
@@ -712,9 +712,194 @@ long elapsedTime = now - millisDateTime;
 String nowString = params['now'];
 ZonedDateTime nowZdt = ZonedDateTime.parse(datetime); <1>
 long now = ZonedDateTime.toInstant().toEpochMilli();
-def inputDateTime = doc['input_datetime'];
+ZonedDateTime inputDateTime = doc['input_datetime'];
 long millisDateTime = zdt.toInstant().toEpochMilli();
 long elapsedTime = now - millisDateTime;
 ----
 <1> Note this parses the same string datetime every time the script runs. Use a
 numeric datetime to avoid a significant performance hit.
+
+==== Datetime Examples in Contexts
+
+===== Load the Example Data
+
+Run the following curl commands to load the data necessary for the context
+examples into an Elasticsearch cluster:
+
+. Create {ref}/mapping.html[mappings] for the sample data.
++
+[source,js]
+----
+PUT /messages
+{
+    "mappings": {
+        "properties": {
+            "priority": {
+                "type": "integer"
+            },
+            "datetime": {
+                "type": "date"
+            },
+            "message": {
+                "type": "text"
+            }
+        }
+    }
+}
+----
++
+// CONSOLE
++
+. Load the sample data.
++
+[source,js]
+----
+POST /_bulk
+{ "index" : { "_index" : "messages", "_id" : "1" } }
+{ "priority": 1, "datetime": "2019-07-17T12:13:14Z", "message": "m1" }
+{ "index" : { "_index" : "messages", "_id" : "2" } }
+{ "priority": 1, "datetime": "2019-07-24T01:14:59Z", "message": "m2" }
+{ "index" : { "_index" : "messages", "_id" : "3" } }
+{ "priority": 2, "datetime": "1983-10-14T00:36:42Z", "message": "m3" }
+{ "index" : { "_index" : "messages", "_id" : "4" } }
+{ "priority": 3, "datetime": "1983-10-10T02:15:15Z", "message": "m4" }
+{ "index" : { "_index" : "messages", "_id" : "5" } }
+{ "priority": 3, "datetime": "1983-10-10T17:18:19Z", "message": "m5" }
+{ "index" : { "_index" : "messages", "_id" : "6" } }
+{ "priority": 1, "datetime": "2019-08-03T17:19:31Z", "message": "m6" }
+{ "index" : { "_index" : "messages", "_id" : "7" } }
+{ "priority": 3, "datetime": "2019-08-04T17:20:00Z", "message": "m7" }
+{ "index" : { "_index" : "messages", "_id" : "8" } }
+{ "priority": 2, "datetime": "2019-08-04T18:01:01Z", "message": "m8" }
+{ "index" : { "_index" : "messages", "_id" : "9" } }
+{ "priority": 3, "datetime": "1983-10-10T19:00:45Z", "message": "m9" }
+{ "index" : { "_index" : "messages", "_id" : "10" } }
+{ "priority": 2, "datetime": "2019-07-23T23:39:54Z", "message": "m10" }
+----
++
+// CONSOLE
+// TEST[continued]
+
+===== Day-of-the-Week Bucket Aggregation Example
+
+The following example uses a
+{ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script[terms aggregation]
+as part of the
+<<painless-bucket-script-agg-context, bucket script aggregation context>> to
+display the number of messages from each day-of-the-week.
+
+[source,js]
+----
+GET /messages/_search?pretty=true
+{
+    "aggs": {
+        "day-of-week-count": {
+            "terms": {
+                "script": "return doc[\"datetime\"].value.getDayOfWeekEnum();"
+            }
+        }
+    }
+}
+----
+// CONSOLE
+// TEST[continued]
+
+===== Morning/Evening Bucket Aggregation Example
+
+The following example uses a
+{ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script[terms aggregation]
+as part of the
+<<painless-bucket-script-agg-context, bucket script aggregation context>> to
+display the number of messages received in the morning versus the evening.
+
+[source,js]
+----
+GET /messages/_search?pretty=true
+{
+    "aggs": {
+        "am-pm-count": {
+            "terms": {
+                "script": "return doc[\"datetime\"].value.getHour() < 12 ? \"AM\" : \"PM\";"
+            }
+        }
+    }
+}
+----
+// CONSOLE
+// TEST[continued]
+
+===== Age of a Message Script Field Example
+
+The following example uses a
+{ref}/search-request-script-fields.html[script field] as part of the
+<<painless-field-context, field context>> to display the elapsed time between
+"now" and when a message was received.
+
+[source,js]
+----
+GET /_search?pretty=true
+{
+    "query" : {
+        "match_all": {}
+    },
+    "script_fields" : {
+        "message_age" : {
+            "script" : {
+                "source": "ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(params[\"now\"]), ZoneId.of(\"Z\")); ZonedDateTime mdt = doc[\"datetime\"].value; String age; long years = mdt.until(now, ChronoUnit.YEARS); age = years + \"Y \"; mdt = mdt.plusYears(years); long months = mdt.until(now, ChronoUnit.MONTHS); age += months + \"M \"; mdt = mdt.plusMonths(months); long days = mdt.until(now, ChronoUnit.DAYS); age += days + \"D \"; mdt = mdt.plusDays(days); long hours = mdt.until(now, ChronoUnit.HOURS); age += hours + \"h \"; mdt = mdt.plusHours(hours); long minutes = mdt.until(now, ChronoUnit.MINUTES); age += minutes + \"m \"; mdt = mdt.plusMinutes(minutes); long seconds = mdt.until(now, ChronoUnit.SECONDS); age += hours + \"s\"; return age;",
+                "params": {
+                    "now": 1574005645830
+                }
+            }
+        }
+    }
+}
+----
+// CONSOLE
+// TEST[continued]
+
+The following shows the script broken into multiple lines:
+
+[source,Painless]
+----
+ZonedDateTime now = ZonedDateTime.ofInstant(
+        Instant.ofEpochMilli(params['now']), ZoneId.of('Z')); <1>
+ZonedDateTime mdt = doc['datetime'].value; <2>
+
+String age;
+
+long years = mdt.until(now, ChronoUnit.YEARS); <3>
+age = years + 'Y '; <4>
+mdt = mdt.plusYears(years); <5>
+
+long months = mdt.until(now, ChronoUnit.MONTHS);
+age += months + 'M ';
+mdt = mdt.plusMonths(months);
+
+long days = mdt.until(now, ChronoUnit.DAYS);
+age += days + 'D ';
+mdt = mdt.plusDays(days);
+
+long hours = mdt.until(now, ChronoUnit.HOURS);
+age += hours + 'h ';
+mdt = mdt.plusHours(hours);
+
+long minutes = mdt.until(now, ChronoUnit.MINUTES);
+age += minutes + 'm ';
+mdt = mdt.plusMinutes(minutes);
+
+long seconds = mdt.until(now, ChronoUnit.SECONDS);
+age += hours + 's';
+
+return age; <6>
+----
+<1> Parse the datetime "now" as input from the user-defined params.
+<2> Store the datetime the message was received as a `ZonedDateTime`.
+<3> Find the difference in years between "now" and the datetime the message was
+received.
+<4> Add the difference in years later returned in the format
+`Y <years> ...` for the age of a message.
+<5> Add the years so only the remainder of the months, days, etc. remain as the
+difference between "now" and the datetime the message was received. Repeat this
+pattern until the desired granularity is reached (seconds in this example).
+<6> Return the age of the message in the format
+`Y <years> M <months> D <days> h <hours> m <minutes> s <seconds>`.

+ 3 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java

@@ -27,6 +27,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
 import org.objectweb.asm.Type;
 
+import java.time.ZonedDateTime;
 import java.util.Objects;
 import java.util.Set;
 
@@ -53,7 +54,8 @@ final class PSubDefArray extends AStoreable {
         index.expected = index.actual;
         index = index.cast(locals);
 
-        actual = expected == null || explicit ? def.class : expected;
+        // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
+        actual = expected == null || expected == ZonedDateTime.class || explicit ? def.class : expected;
     }
 
     @Override

+ 3 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java

@@ -27,6 +27,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
 import org.objectweb.asm.Type;
 
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -84,7 +85,8 @@ final class PSubDefCall extends AExpression {
             arguments.set(argument, expression.cast(locals));
         }
 
-        actual = expected == null || explicit ? def.class : expected;
+        // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
+        actual = expected == null || expected == ZonedDateTime.class || explicit ? def.class : expected;
     }
 
     @Override

+ 3 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java

@@ -26,6 +26,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
 
+import java.time.ZonedDateTime;
 import java.util.Objects;
 import java.util.Set;
 
@@ -49,7 +50,8 @@ final class PSubDefField extends AStoreable {
 
     @Override
     void analyze(Locals locals) {
-        actual = expected == null || explicit ? def.class : expected;
+        // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
+        actual = expected == null || expected == ZonedDateTime.class || explicit ? def.class : expected;
     }
 
     @Override

+ 4 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/DefCastTests.java

@@ -689,7 +689,10 @@ public class DefCastTests extends ScriptTestCase {
         assertEquals(0L, exec(
                 "Instant instant = Instant.ofEpochMilli(434931330000L);" +
                 "def d = new JodaCompatibleZonedDateTime(instant, ZoneId.of('Z'));" +
-                "ZonedDateTime t = d;" +
+                "def x = new HashMap(); x.put('dt', d);" +
+                "ZonedDateTime t = x['dt'];" +
+                "def y = t;" +
+                "t = y;" +
                 "return ChronoUnit.MILLIS.between(d, t);"
         ));
     }