Explorar o código

Add a transformer to translate constant BigDecimal to double

Lee Hinman %!s(int64=11) %!d(string=hai) anos
pai
achega
b43b56a6a8

+ 0 - 10
docs/reference/modules/scripting.asciidoc

@@ -390,16 +390,6 @@ power of the second argument.
 or underflow.
 |=======================================================================
 
-[float]
-=== Floating point numbers in Groovy
-
-When using floating-point literals in Groovy scripts, Groovy will automatically
-use BigDecimal instead of a floating point equivalent to support the
-'least-surprising' approach to literal math operations. To use a floating-point
-number instead, use the specific suffix on the number (ie, instead of 1.2, use
-1.2f). See the http://groovy.codehaus.org/Groovy+Math[Groovy Math] page for more
-information.
-
 [float]
 === Arithmetic precision in MVEL
 

+ 1 - 0
src/main/java/org/elasticsearch/script/groovy/GroovySandboxExpressionChecker.java

@@ -81,6 +81,7 @@ public class GroovySandboxExpressionChecker implements SecureASTCustomizer.Expre
             java.util.HashMap.class.getName(),
             java.util.HashSet.class.getName(),
             java.util.UUID.class.getName(),
+            java.math.BigDecimal.class.getName(),
             org.joda.time.DateTime.class.getName(),
             org.joda.time.DateTimeZone.class.getName()
     };

+ 57 - 0
src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java

@@ -24,7 +24,16 @@ import groovy.lang.GroovyClassLoader;
 import groovy.lang.Script;
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.search.Scorer;
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.classgen.GeneratorContext;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.customizers.CompilationCustomizer;
 import org.codehaus.groovy.control.customizers.ImportCustomizer;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.component.AbstractComponent;
@@ -37,6 +46,7 @@ import org.elasticsearch.script.SearchScript;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
@@ -64,6 +74,8 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
         if (this.sandboxed) {
             config.addCompilationCustomizers(GroovySandboxExpressionChecker.getSecureASTCustomizer(settings));
         }
+        // Add BigDecimal -> Double transformer
+        config.addCompilationCustomizers(new GroovyBigDecimalTransformer(CompilePhase.CONVERSION));
         this.loader = new GroovyClassLoader(settings.getClassLoader(), config);
     }
 
@@ -282,6 +294,51 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
                 return value;
             }
         }
+    }
+
+    /**
+     * A compilation customizer that is used to transform a number like 1.23,
+     * which would normally be a BigDecimal, into a double value.
+     */
+    private class GroovyBigDecimalTransformer extends CompilationCustomizer {
+
+        private GroovyBigDecimalTransformer(CompilePhase phase) {
+            super(phase);
+        }
+
+        @Override
+        public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
+            new BigDecimalExpressionTransformer(source).visitClass(classNode);
+        }
+    }
+
+    /**
+     * Groovy expression transformer that converts BigDecimals to doubles
+     */
+    private class BigDecimalExpressionTransformer extends ClassCodeExpressionTransformer {
+
+        private final SourceUnit source;
 
+        private BigDecimalExpressionTransformer(SourceUnit source) {
+            this.source = source;
+        }
+
+        @Override
+        protected SourceUnit getSourceUnit() {
+            return this.source;
+        }
+
+        @Override
+        public Expression transform(Expression expr) {
+            Expression newExpr = expr;
+            if (expr instanceof ConstantExpression) {
+                ConstantExpression constExpr = (ConstantExpression) expr;
+                Object val = constExpr.getValue();
+                if (val != null && val instanceof BigDecimal) {
+                    newExpr = new ConstantExpression(((BigDecimal) val).doubleValue());
+                }
+            }
+            return super.transform(newExpr);
+        }
     }
 }

+ 50 - 0
src/test/java/org/elasticsearch/script/GroovyScriptTests.java

@@ -0,0 +1,50 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.script;
+
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.test.ElasticsearchIntegrationTest;
+import org.junit.Test;
+
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
+
+/**
+ * Various tests for Groovy scripting
+ */
+public class GroovyScriptTests extends ElasticsearchIntegrationTest {
+
+    @Test
+    public void testGroovyBigDecimalTransformation() {
+        client().prepareIndex("test", "doc", "1").setSource("foo", 5).setRefresh(true).get();
+
+        // Test that something that would usually be a BigDecimal is transformed into a Double
+        assertScript("def n = 1.23; assert n instanceof Double;");
+        assertScript("def n = 1.23G; assert n instanceof Double;");
+        assertScript("def n = BigDecimal.ONE; assert n instanceof BigDecimal;");
+    }
+
+    public void assertScript(String script) {
+        SearchResponse resp = client().prepareSearch("test")
+                .setSource("{\"query\": {\"match_all\": {}}," +
+                        "\"sort\":{\"_script\": {\"script\": \""+ script +
+                        "; 1\", \"type\": \"number\", \"lang\": \"groovy\"}}}").get();
+        assertNoFailures(resp);
+    }
+}