diff --git a/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java b/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java index 7713157422efe..5d1250a51482a 100644 --- a/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java +++ b/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java @@ -38,7 +38,7 @@ public GeoPoint() { } /** - * Create a new Geopointform a string. This String must either be a geohash + * Create a new Geopoint from a string. This String must either be a geohash * or a lat-lon tuple. * * @param value String to create the point from diff --git a/docs/reference/modules/scripting/painless.asciidoc b/docs/reference/modules/scripting/painless.asciidoc index 98cb052d7a3f8..b8c4c2cc81f44 100644 --- a/docs/reference/modules/scripting/painless.asciidoc +++ b/docs/reference/modules/scripting/painless.asciidoc @@ -115,9 +115,8 @@ GET hockey/_search ---------------------------------------------------------------- // AUTOSENSE -You must always specify the index of the field value you want, even if there's only a single item in the field. -All fields in Elasticsearch are multi-valued and Painless does not provide a `.value` shortcut. The following example uses a Painless script to sort the players by their combined first and last names. The names are accessed using -`input.doc['first'].0` and `input.doc['last'].0`. +The following example uses a Painless script to sort the players by their combined first and last names. The names are accessed using +`input.doc['first'].value` and `input.doc['last'].value`. [source,js] ---------------------------------------------------------------- @@ -132,7 +131,7 @@ GET hockey/_search "order": "asc", "script": { "lang": "painless", - "inline": "input.doc['first'].0 + ' ' + input.doc['last'].0" + "inline": "input.doc['first'].value + ' ' + input.doc['last'].value" } } } @@ -218,7 +217,7 @@ GET hockey/_search "full_name_dynamic": { "script": { "lang": "painless", - "inline": "def first = input.doc['first'].0; def last = input.doc['last'].0; return first + ' ' + last;" + "inline": "def first = input.doc['first'].value; def last = input.doc['last'].value; return first + ' ' + last;" } }, "full_name_static": { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java index 4e170c93e49a6..136746026fa10 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless; +import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Field; import org.elasticsearch.painless.Definition.Method; @@ -119,9 +120,22 @@ public static void fieldStore(final Object owner, Object value, final String nam @SuppressWarnings("rawtypes") public static Object fieldLoad(final Object owner, final String name, final Definition definition) { - if (owner.getClass().isArray() && "length".equals(name)) { + final Class clazz = owner.getClass(); + if (clazz.isArray() && "length".equals(name)) { return Array.getLength(owner); } else { + // TODO: remove this fast-path, once we speed up dynamics some more + if ("value".equals(name) && owner instanceof ScriptDocValues) { + if (clazz == ScriptDocValues.Doubles.class) { + return ((ScriptDocValues.Doubles)owner).getValue(); + } else if (clazz == ScriptDocValues.Longs.class) { + return ((ScriptDocValues.Longs)owner).getValue(); + } else if (clazz == ScriptDocValues.Strings.class) { + return ((ScriptDocValues.Strings)owner).getValue(); + } else if (clazz == ScriptDocValues.GeoPoints.class) { + return ((ScriptDocValues.GeoPoints)owner).getValue(); + } + } final Field field = getField(owner, name, definition); MethodHandle handle; @@ -143,7 +157,7 @@ public static Object fieldLoad(final Object owner, final String name, final Defi } } else { throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " + - "for class [" + owner.getClass().getCanonicalName() + "]."); + "for class [" + clazz.getCanonicalName() + "]."); } } else { handle = field.getter; @@ -151,13 +165,13 @@ public static Object fieldLoad(final Object owner, final String name, final Defi if (handle == null) { throw new IllegalArgumentException( - "Unable to read from field [" + name + "] with owner class [" + owner.getClass() + "]."); + "Unable to read from field [" + name + "] with owner class [" + clazz + "]."); } else { try { return handle.invoke(owner); } catch (final Throwable throwable) { throw new IllegalArgumentException("Error loading value from " + - "field [" + name + "] with owner class [" + owner.getClass() + "].", throwable); + "field [" + name + "] with owner class [" + clazz + "].", throwable); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index 450f2aa6fa4d4..5a0e0c1e63635 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -33,6 +33,9 @@ import java.util.Map; import java.util.Set; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.index.fielddata.ScriptDocValues; + class Definition { enum Sort { VOID( void.class , 0 , true , false , false , false ), @@ -393,6 +396,14 @@ private Transform(final Cast cast, Method method, final Type upcast, final Type final Type iargexcepType; final Type istateexceptType; final Type nfexcepType; + + // docvalues accessors + final Type geoPointType; + final Type stringsType; + // TODO: add ReadableDateTime? or don't expose the joda stuff? + final Type longsType; + final Type doublesType; + final Type geoPointsType; public Definition() { structs = new HashMap<>(); @@ -471,6 +482,12 @@ public Definition() { istateexceptType = getType("IllegalStateException"); nfexcepType = getType("NumberFormatException"); + geoPointType = getType("GeoPoint"); + stringsType = getType("Strings"); + longsType = getType("Longs"); + doublesType = getType("Doubles"); + geoPointsType = getType("GeoPoints"); + addDefaultElements(); copyDefaultStructs(); addDefaultTransforms(); @@ -564,6 +581,12 @@ public Definition() { iargexcepType = definition.iargexcepType; istateexceptType = definition.istateexceptType; nfexcepType = definition.nfexcepType; + + geoPointType = definition.geoPointType; + stringsType = definition.stringsType; + longsType = definition.longsType; + doublesType = definition.doublesType; + geoPointsType = definition.geoPointsType; } private void addDefaultStructs() { @@ -634,6 +657,12 @@ private void addDefaultStructs() { addStruct( "IllegalArgumentException" , IllegalArgumentException.class); addStruct( "IllegalStateException" , IllegalStateException.class); addStruct( "NumberFormatException" , NumberFormatException.class); + + addStruct( "GeoPoint" , GeoPoint.class); + addStruct( "Strings" , ScriptDocValues.Strings.class); + addStruct( "Longs" , ScriptDocValues.Longs.class); + addStruct( "Doubles" , ScriptDocValues.Doubles.class); + addStruct( "GeoPoints" , ScriptDocValues.GeoPoints.class); } private void addDefaultClasses() { @@ -670,6 +699,12 @@ private void addDefaultClasses() { addClass("HashMap"); addClass("Exception"); + + addClass("GeoPoint"); + addClass("Strings"); + addClass("Longs"); + addClass("Doubles"); + addClass("GeoPoints"); } private void addDefaultElements() { @@ -1032,6 +1067,61 @@ private void addDefaultElements() { addConstructor("IllegalStateException", "new", new Type[] {stringType}, null); addConstructor("NumberFormatException", "new", new Type[] {stringType}, null); + + addMethod("GeoPoint", "getLat", null, false, doubleType, new Type[] {}, null, null); + addMethod("GeoPoint", "getLon", null, false, doubleType, new Type[] {}, null, null); + addMethod("Strings", "getValue", null, false, stringType, new Type[] {}, null, null); + addMethod("Strings", "getValues", null, false, slistType, new Type[] {}, null, null); + addMethod("Longs", "getValue", null, false, longType, new Type[] {}, null, null); + addMethod("Longs", "getValues", null, false, olistType, new Type[] {}, null, null); + // TODO: add better date support for Longs here? (carefully?) + addMethod("Doubles", "getValue", null, false, doubleType, new Type[] {}, null, null); + addMethod("Doubles", "getValues", null, false, olistType, new Type[] {}, null, null); + addMethod("GeoPoints", "getValue", null, false, geoPointType, new Type[] {}, null, null); + addMethod("GeoPoints", "getValues", null, false, olistType, new Type[] {}, null, null); + addMethod("GeoPoints", "getLat", null, false, doubleType, new Type[] {}, null, null); + addMethod("GeoPoints", "getLon", null, false, doubleType, new Type[] {}, null, null); + addMethod("GeoPoints", "getLats", null, false, getType(doubleType.struct, 1), new Type[] {}, null, null); + addMethod("GeoPoints", "getLons", null, false, getType(doubleType.struct, 1), new Type[] {}, null, null); + // geo distance functions... so many... + addMethod("GeoPoints", "factorDistance", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "factorDistanceWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "factorDistance02", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "factorDistance13", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistance", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistanceWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistanceInKm", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistanceInKmWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistanceInMiles", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "arcDistanceInMilesWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distance", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distanceWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distanceInKm", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distanceInKmWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distanceInMiles", null, false, doubleType, + new Type[] { doubleType, doubleType }, null, null); + addMethod("GeoPoints", "distanceInMilesWithDefault", null, false, doubleType, + new Type[] { doubleType, doubleType, doubleType }, null, null); + addMethod("GeoPoints", "geohashDistance", null, false, doubleType, + new Type[] { stringType }, null, null); + addMethod("GeoPoints", "geohashDistanceInKm", null, false, doubleType, + new Type[] { stringType }, null, null); + addMethod("GeoPoints", "geohashDistanceInMiles", null, false, doubleType, + new Type[] { stringType }, null, null); } private void copyDefaultStructs() { @@ -1079,6 +1169,12 @@ private void copyDefaultStructs() { copyStruct("IllegalArgumentException", "Exception", "Object"); copyStruct("IllegalStateException", "Exception", "Object"); copyStruct("NumberFormatException", "Exception", "Object"); + + copyStruct("GeoPoint", "Object"); + copyStruct("Strings", "List", "Collection", "Object"); + copyStruct("Longs", "List", "Collection", "Object"); + copyStruct("Doubles", "List", "Collection", "Object"); + copyStruct("GeoPoints", "List", "Collection", "Object"); } private void addDefaultTransforms() { diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/20_scriptfield.yaml b/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/20_scriptfield.yaml index d53306b5d47fa..a1087f17d4e4f 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/20_scriptfield.yaml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/20_scriptfield.yaml @@ -28,7 +28,7 @@ setup: script_fields: bar: script: - inline: "input.doc['foo'].0 + input.x;" + inline: "input.doc['foo'].value + input.x;" lang: painless params: x: "bbb" diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/30_search.yaml b/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/30_search.yaml index 2dd1a6004ff50..4a1ec86a26764 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/30_search.yaml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/30_search.yaml @@ -29,12 +29,12 @@ query: script: script: - inline: "input.doc['num1'].0 > 1;" + inline: "input.doc['num1'].value > 1;" lang: painless script_fields: sNum1: script: - inline: "input.doc['num1'].0;" + inline: "input.doc['num1'].value;" lang: painless sort: num1: @@ -51,7 +51,7 @@ query: script: script: - inline: "input.doc['num1'].0 > input.param1;" + inline: "input.doc['num1'].value > input.param1;" lang: painless params: param1: 1 @@ -59,7 +59,7 @@ script_fields: sNum1: script: - inline: "return input.doc['num1'].0;" + inline: "return input.doc['num1'].value;" lang: painless sort: num1: @@ -76,7 +76,7 @@ query: script: script: - inline: "input.doc['num1'].0 > input.param1;" + inline: "input.doc['num1'].value > input.param1;" lang: painless params: param1: -1 @@ -84,7 +84,7 @@ script_fields: sNum1: script: - inline: "input.doc['num1'].0;" + inline: "input.doc['num1'].value;" lang: painless sort: num1: