Add a transformer to translate constant BigDecimal to double

This commit is contained in:
Lee Hinman 2014-06-23 16:21:29 +02:00
parent 50bb274efa
commit b43b56a6a8
4 changed files with 108 additions and 10 deletions

View File

@ -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

View File

@ -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()
};

View File

@ -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);
}
}
}

View File

@ -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);
}
}