Skip to content

Commit

Permalink
Add a transformer to translate constant BigDecimal to double
Browse files Browse the repository at this point in the history
  • Loading branch information
dakrone committed Jun 26, 2014
1 parent 50bb274 commit b43b56a
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 10 deletions.
10 changes: 0 additions & 10 deletions docs/reference/modules/scripting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public GroovySandboxExpressionChecker(Settings settings) {
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()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
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;
Expand All @@ -37,6 +46,7 @@
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;
Expand Down Expand Up @@ -64,6 +74,8 @@ public GroovyScriptEngineService(Settings settings) {
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);
}

Expand Down Expand Up @@ -282,6 +294,51 @@ public double doubleValue() {
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 changes: 50 additions & 0 deletions src/test/java/org/elasticsearch/script/GroovyScriptTests.java
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit b43b56a

Please sign in to comment.