blob: f38ddeba6bc88c89a7085fbfb66cbb8be44f3c19 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.tinkerpop.gremlin.process.traversal.translator;
import org.apache.tinkerpop.gremlin.jsr223.TranslatorCustomizer;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.junit.Test;
import javax.script.Bindings;
import javax.script.SimpleBindings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.function.Function;
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel;
import static org.junit.Assert.assertEquals;
/**
* test {@link GroovyTranslator} which return parameterized result, covers:
* - parameterized script checking
* - binding checking
* - eval result checking
*
* <p>
* {@link GroovyTranslatorTest } is used to test {@link GroovyTranslator}, both test cases looks the same
* <p>
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Stephen Mallette (http://stephen.genoprime.com)
* @author Stark Arya (sandszhou.zj@alibaba-inc.com)
*/
public class ParameterizedGroovyTranslatorTest {
private static final GraphTraversalSource g = traversal().withEmbedded(EmptyGraph.instance());
private static final Translator.ScriptTranslator translator = GroovyTranslator.of("g", true);
@Test
public void shouldHandleStrategies() throws Exception {
assertEquals("g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(checkAdjacentVertices: _args_0, vertices: __.hasLabel(_args_1))).V().has(_args_2)",
translator.translate(g.withStrategies(ReadOnlyStrategy.instance(),
SubgraphStrategy.build().checkAdjacentVertices(false).vertices(hasLabel("person")).create()).
V().has("name").asAdmin().getBytecode()).getScript());
}
@Test
public void shouldSupportStringSupplierLambdas() {
final GraphTraversal.Admin<Vertex, Integer> t = g.withSideEffect("lengthSum", 0).withSack(1)
.V()
.filter(Lambda.predicate("it.get().label().equals('person')"))
.flatMap(Lambda.function("it.get().vertices(Direction.OUT)"))
.map(Lambda.<Traverser<Object>, Integer>function("it.get().value('name').length()"))
.sideEffect(Lambda.consumer("{ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) }"))
.order().by(Lambda.comparator("a,b -> a <=> b"))
.sack(Lambda.biFunction("{ a,b -> a + b }"))
.asAdmin();
final Script script = translator.translate(t.getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(9, bindings.size());
assertEquals("lengthSum", bindings.get("_args_0"));
assertEquals(Integer.valueOf(0), bindings.get("_args_1"));
assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
assertEquals(Lambda.predicate("it.get().label().equals('person')"), bindings.get("_args_3"));
assertEquals(Lambda.function("it.get().vertices(Direction.OUT)"), bindings.get("_args_4"));
assertEquals(Lambda.<Traverser<Object>, Integer>function("it.get().value('name').length()"), bindings.get("_args_5"));
assertEquals(Lambda.consumer("{ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) }"), bindings.get("_args_6"));
assertEquals(Lambda.comparator("a,b -> a <=> b"), bindings.get("_args_7"));
assertEquals(Lambda.biFunction("{ a,b -> a + b }"), bindings.get("_args_8"));
assertEquals("g.withSideEffect(_args_0,_args_1).withSack(_args_2)" +
".V()" +
".filter(_args_3)" +
".flatMap(_args_4)" +
".map(_args_5)" +
".sideEffect(_args_6)" +
".order().by(_args_7)" +
".sack(_args_8)",
script.getScript());
}
@Test
public void shouldHandleArray() {
final Script script = translator.translate(g.V().has(T.id, P.within(new ArrayList() {{
add(1);
add(2);
add(3);
add(4);
add(5);
}})).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(5, bindings.size());
assertEquals(Integer.valueOf(1), bindings.get("_args_0"));
assertEquals(Integer.valueOf(2), bindings.get("_args_1"));
assertEquals(Integer.valueOf(3), bindings.get("_args_2"));
assertEquals(Integer.valueOf(4), bindings.get("_args_3"));
assertEquals(Integer.valueOf(5), bindings.get("_args_4"));
assertEquals("g.V().has(T.id,P.within([_args_0, _args_1, _args_2, _args_3, _args_4]))", script.getScript());
}
@Test
public void shouldHandleSet() {
final Script script = translator.translate(g.V().id().is(new HashSet<Object>() {{
add(3);
add(Arrays.asList(1, 2, 3.1d));
add(3);
add("3");
}}).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(5, bindings.size());
assertEquals(Integer.valueOf(3), bindings.get("_args_0"));
assertEquals("3", bindings.get("_args_1"));
assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
assertEquals(Integer.valueOf(2), bindings.get("_args_3"));
assertEquals(Double.valueOf(3.1), bindings.get("_args_4"));
assertEquals("g.V().id().is([_args_0, _args_1, [_args_2, _args_3, _args_4]] as Set)", script.getScript());
}
@Test
public void shouldHandleMaps() {
final Script script = translator.translate(g.V().id().is(new LinkedHashMap<Object,Object>() {{
put(3, "32");
put(Arrays.asList(1, 2, 3.1d), 4);
}}).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(6, bindings.size());
assertEquals(Integer.valueOf(3), bindings.get("_args_0"));
assertEquals("32", bindings.get("_args_1"));
assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
assertEquals(Integer.valueOf(2), bindings.get("_args_3"));
assertEquals(Double.valueOf(3.1), bindings.get("_args_4"));
assertEquals(Integer.valueOf(4), bindings.get("_args_5"));
assertEquals("g.V().id().is([(_args_0):(_args_1),([_args_2, _args_3, _args_4]):(_args_5)])", script.getScript());
}
@Test
public void shouldHandleEmptyMaps() {
final Function identity = new Lambda.OneArgLambda("it.get()", "gremlin-groovy");
final Script script = translator.translate(g.inject(Collections.emptyMap()).map(identity).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(1, bindings.size());
assertEquals(identity, bindings.get("_args_0"));
assertEquals("g.inject([]).map(_args_0)", script.getScript());
}
@Test
public void shouldIncludeCustomTypeTranslationForSomethingSilly() throws Exception {
final ParameterizedSillyClass notSillyEnough = ParameterizedSillyClass.from("not silly enough", 100);
// without type translation we get uglinesss
final Script parameterizedScriptBad = translator.translate(g.inject(notSillyEnough).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
parameterizedScriptBad.getParameters().ifPresent(bindings::putAll);
assertEquals(String.format("g.inject(%s)", "_args_0"), parameterizedScriptBad.getScript());
assertEquals(1, bindings.size());
assertEquals(notSillyEnough, bindings.get("_args_0"));
bindings.clear();
// with type translation we get valid gremlin
final Script parameterizedScriptGood = GroovyTranslator.of("g", new ParameterizedSillyClassTranslatorCustomizer().createTypeTranslator()).
translate(g.inject(notSillyEnough).asAdmin().getBytecode());
parameterizedScriptGood.getParameters().ifPresent(bindings::putAll);
assertEquals(2, bindings.size());
assertEquals(notSillyEnough.getX(), bindings.get("_args_0"));
assertEquals(notSillyEnough.getY(), bindings.get("_args_1"));
assertEquals("g.inject(org.apache.tinkerpop.gremlin.process.traversal.translator.ParameterizedGroovyTranslatorTest.ParameterizedSillyClass.from(_args_0,_args_1))",
parameterizedScriptGood.getScript());
}
@Test
public void shouldHaveValidToString() {
assertEquals("translator[h:gremlin-groovy]", GroovyTranslator.of("h", true).toString());
}
@Test
public void shouldEscapeStrings() {
final Script script = translator.translate(g.addV("customer")
.property("customer_id", 501L)
.property("name", "Foo\u0020Bar")
.property("age", 25)
.property("special", "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")
.asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script.getParameters().ifPresent(bindings::putAll);
assertEquals(9, bindings.size());
assertEquals("customer", bindings.get("_args_0"));
assertEquals("customer_id", bindings.get("_args_1"));
assertEquals(Long.valueOf(501), bindings.get("_args_2"));
assertEquals("name", bindings.get("_args_3"));
assertEquals("Foo\u0020Bar", bindings.get("_args_4"));
assertEquals("age", bindings.get("_args_5"));
assertEquals(Integer.valueOf(25), bindings.get("_args_6"));
assertEquals("special", bindings.get("_args_7"));
assertEquals("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", bindings.get("_args_8"));
assertEquals("g.addV(_args_0).property(_args_1,_args_2).property(_args_3,_args_4).property(_args_5,_args_6).property(_args_7,_args_8)", script.getScript());
}
@Test
public void shouldHandleVertexAndEdge() {
final Object id1 = "customer:10:foo\u0020bar\u0020\u0024100#90"; // customer:10:foo bar $100#90
final Vertex vertex1 = DetachedVertex.build().setLabel("customer").setId(id1)
.create();
final Script script1 = translator.translate(g.inject(vertex1).asAdmin().getBytecode());
final Bindings bindings = new SimpleBindings();
script1.getParameters().ifPresent(bindings::putAll);
assertEquals(2, bindings.size());
assertEquals(id1, bindings.get("_args_0"));
assertEquals("customer", bindings.get("_args_1"));
assertEquals("g.inject(new ReferenceVertex(_args_0,_args_1))", script1.getScript());
bindings.clear();
final Object id2 = "user:20:foo\\u0020bar\\u005c\\u0022mr\\u005c\\u0022\\u00241000#50"; // user:20:foo\u0020bar\u005c\u0022mr\u005c\u0022\u00241000#50
final Vertex vertex2 = DetachedVertex.build().setLabel("user").setId(id2)
.create();
final Script script2 = translator.translate(g.inject(vertex2).asAdmin().getBytecode());
script2.getParameters().ifPresent(bindings::putAll);
assertEquals(2, bindings.size());
assertEquals(id2, bindings.get("_args_0"));
assertEquals("user", bindings.get("_args_1"));
assertEquals("g.inject(new ReferenceVertex(_args_0,_args_1))", script2.getScript());
bindings.clear();
final Object id3 = "knows:30:foo\u0020bar\u0020\u0024100:\\u0020\\u0024500#70";
final Edge edge = DetachedEdge.build().setLabel("knows").setId(id3)
.setOutV((DetachedVertex) vertex1)
.setInV((DetachedVertex) vertex2)
.create();
final Script script3 = translator.translate(g.inject(edge).asAdmin().getBytecode());
script3.getParameters().ifPresent(bindings::putAll);
assertEquals(6, bindings.size());
assertEquals(id3, bindings.get("_args_0"));
assertEquals("knows", bindings.get("_args_1"));
assertEquals(id2, bindings.get("_args_2"));
assertEquals("user", bindings.get("_args_3"));
assertEquals(id1, bindings.get("_args_4"));
assertEquals("customer", bindings.get("_args_5"));
assertEquals("g.inject(new ReferenceEdge(_args_0,_args_1,new ReferenceVertex(_args_2,_args_3),new ReferenceVertex(_args_4,_args_5)))", script3.getScript());
bindings.clear();
final Script script4 = translator.translate(
g.addE("knows").from(vertex1).to(vertex2).property("when", "2018/09/21")
.asAdmin().getBytecode());
script4.getParameters().ifPresent(bindings::putAll);
assertEquals(7, bindings.size());
assertEquals("knows", bindings.get("_args_0"));
assertEquals(id1, bindings.get("_args_1"));
assertEquals("customer", bindings.get("_args_2"));
assertEquals(id2, bindings.get("_args_3"));
assertEquals("user", bindings.get("_args_4"));
assertEquals("when", bindings.get("_args_5"));
assertEquals("2018/09/21", bindings.get("_args_6"));
assertEquals("g.addE(_args_0).from(new ReferenceVertex(_args_1,_args_2)).to(new ReferenceVertex(_args_3,_args_4)).property(_args_5,_args_6)", script4.getScript());
bindings.clear();
final Script script5 = translator.translate(g.V().has("age").asAdmin().getBytecode());
script5.getParameters().ifPresent(bindings::putAll);
assertEquals(1, bindings.size());
assertEquals("age", bindings.get("_args_0"));
assertEquals("g.V().has(_args_0)", script5.getScript());
}
public static class ParameterizedSillyClass {
private final String x;
private final int y;
private ParameterizedSillyClass(final String x, final int y) {
this.x = x;
this.y = y;
}
public static ParameterizedSillyClass from(final String x, final int y) {
return new ParameterizedSillyClass(x, y);
}
public String getX() {
return x;
}
public int getY() {
return y;
}
public Object[] getArguments() {
return new Object[] {x,y};
}
@Override
public String toString() {
return String.format("org.apache.tinkerpop.gremlin.groovy.jsr223.ParameterizedGroovyTranslatorTest.ParameterizedSillyClass.from('%s', (int) %s)", getX(), getY());
}
}
public static class ParameterizedSillyClassTranslator extends GroovyTranslator.DefaultTypeTranslator {
public ParameterizedSillyClassTranslator(final boolean withParameters) {
super(withParameters);
}
@Override
protected Script convertToScript(final Object object) {
if (object instanceof ParameterizedSillyClass) {
ParameterizedSillyClass obj = (ParameterizedSillyClass) object;
script.append(obj.getClass().getCanonicalName());
if (0 == obj.getArguments().length) {
script.append(".").append("from").append("()");
} else {
script.append(".").append("from").append("(");
for (final Object argument: obj.getArguments()) {
convertToScript(argument);
script.append(",");
}
script.setCharAtEnd(')');
}
return script;
} else {
return super.convertToScript(object);
}
}
}
public static class ParameterizedSillyClassTranslatorCustomizer implements TranslatorCustomizer {
@Override
public Translator.ScriptTranslator.TypeTranslator createTypeTranslator() {
return new ParameterizedSillyClassTranslator(true);
}
}
}