Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework procedure API to support translator-local definitions #95

Merged
merged 3 commits into from
May 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,72 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.opencypher.gremlin.extension.CypherArgument.argument;
import static org.opencypher.gremlin.extension.CypherBinding.binding;
import static org.opencypher.gremlin.extension.CypherProcedure.cypherProcedure;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

public class TestProcedureProvider implements CypherProcedureProvider {
@Override
public void apply(CypherProcedureRegistrar registry) {
registry.register(
public final class TestProcedures implements Supplier<Set<CypherProcedure>> {

private final Set<CypherProcedure> procedures = new HashSet<>();

public TestProcedures() {
procedures.add(cypherProcedure(
"test.getName",
emptyList(),
singletonList(argument("name", String.class)),
singletonList(binding("name", String.class)),
arguments -> asList(
singletonMap("name", "marko"),
singletonMap("name", "vadas")
)
);
));

registry.register(
procedures.add(cypherProcedure(
"test.inc",
singletonList(argument("a", Long.class)),
singletonList(argument("r", Long.class)),
singletonList(binding("a", Long.class)),
singletonList(binding("r", Long.class)),
arguments -> {
long a = (long) arguments.get("a");
return singletonList(singletonMap("r", a + 1));
}
);
));

registry.register(
procedures.add(cypherProcedure(
"test.incF",
singletonList(argument("a", Double.class)),
singletonList(argument("r", Double.class)),
singletonList(binding("a", Double.class)),
singletonList(binding("r", Double.class)),
arguments -> {
double a = (double) arguments.get("a");
return singletonList(singletonMap("r", a + 1));
}
);
));

registry.register(
procedures.add(cypherProcedure(
"test.multi",
emptyList(),
asList(argument("foo", String.class), argument("bar", String.class)),
asList(binding("foo", String.class), binding("bar", String.class)),
arguments -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("bar", "bar");
row.put("foo", "foo");
return singletonList(row);
}
);
));

registry.register(
procedures.add(cypherProcedure(
"test.void",
emptyList(),
emptyList(),
arguments -> emptyList()
);
));
}

@Override
public Set<CypherProcedure> get() {
return new HashSet<>(procedures);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,54 @@

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;

import java.util.List;
import java.util.Map;
import org.junit.ClassRule;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal;
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.tinkergraph.structure.TinkerGraph;
import org.junit.Test;
import org.opencypher.gremlin.rules.GremlinServerExternalResource;
import org.junit.experimental.categories.Category;
import org.opencypher.gremlin.extension.TestProcedures;
import org.opencypher.gremlin.groups.SkipWithBytecode;
import org.opencypher.gremlin.groups.SkipWithGremlinGroovy;
import org.opencypher.gremlin.translation.CypherAstWrapper;
import org.opencypher.gremlin.translation.translator.Translator;
import org.opencypher.gremlin.traversal.ReturnNormalizer;

/**
* @see org.opencypher.gremlin.extension.TestProcedureProvider
* @see TestProcedures
*/
@Category({
SkipWithBytecode.class,
SkipWithGremlinGroovy.class
})
public class ProcedureTest {

@ClassRule
public static final GremlinServerExternalResource gremlinServer = new GremlinServerExternalResource();
private GraphTraversalSource gts = TinkerGraph.open().traversal();
private TestProcedures testProcedures = new TestProcedures();

private List<Map<String, Object>> submitAndGet(String cypher) {
return submitAndGet(cypher, emptyMap());
}

private List<Map<String, Object>> submitAndGet(String cypher, Map<String, ?> parameters) {
return gremlinServer.cypherGremlinClient().submit(cypher, parameters).all();
DefaultGraphTraversal g = new DefaultGraphTraversal(gts);
Translator<GraphTraversal, P> translator = Translator.builder()
.traversal(g)
.procedures(testProcedures.get())
.build();
CypherAstWrapper ast = CypherAstWrapper.parse(cypher, parameters);
GraphTraversal<?, ?> traversal = ast.buildTranslation(translator);
ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes());
return traversal.toStream()
.map(returnNormalizer::normalize)
.collect(toList());
}

@Test
Expand Down Expand Up @@ -83,14 +108,14 @@ public void callYieldNothing() {
@Test
public void matchYieldNothing() {
List<Map<String, Object>> results = submitAndGet(
"MATCH (s:software) " +
"UNWIND ['foo', 'bar'] AS r " +
"CALL test.void() " +
"RETURN s.name"
"RETURN r"
);

assertThat(results)
.extracting("s.name")
.containsExactlyInAnyOrder("lop", "ripple");
.extracting("r")
.containsExactlyInAnyOrder("foo", "bar");
}

@Test
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opencypher.gremlin.tck;
package org.opencypher.gremlin.traversal;

import static java.util.stream.Collectors.toList;
import static org.opencypher.gremlin.extension.CypherArgument.argument;
import static org.opencypher.gremlin.extension.CypherBinding.binding;
import static org.opencypher.gremlin.extension.CypherProcedure.cypherProcedure;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.opencypher.gremlin.extension.CypherArgument;
import org.opencypher.gremlin.traversal.ProcedureRegistry;
import org.opencypher.gremlin.extension.CypherBinding;

public final class PredefinedProcedureRegistry {
private PredefinedProcedureRegistry() {
Expand All @@ -42,10 +42,10 @@ public static void register(String signature, List<String> header, List<Map<Stri
throw new IllegalArgumentException("Unparsable procedure signature: " + signature);
}
String name = signatureMatcher.group("name");
List<CypherArgument> arguments = matchArguments(signatureMatcher.group("arguments"));
List<CypherArgument> results = matchArguments(signatureMatcher.group("results"));
List<CypherBinding> arguments = matchArguments(signatureMatcher.group("arguments"));
List<CypherBinding> results = matchArguments(signatureMatcher.group("results"));

ProcedureRegistry.register(registry -> registry.register(
ProcedureContext.global().unsafeRegister(cypherProcedure(
name,
arguments,
results,
Expand All @@ -56,19 +56,24 @@ public static void register(String signature, List<String> header, List<Map<Stri
.filter(row -> extractKeys(in, row).equals(args))
.map(row -> extractKeys(out, row))
.collect(toList());
}));
}
));
}

private static List<CypherArgument> matchArguments(String input) {
List<CypherArgument> arguments = new ArrayList<>();
public static void clear() {
ProcedureContext.global().unsafeClear();
}

private static List<CypherBinding> matchArguments(String input) {
List<CypherBinding> arguments = new ArrayList<>();
if (input == null) {
return arguments;
}
Matcher matcher = ARGUMENT_PATTERN.matcher(input);
while (matcher.find()) {
String name = matcher.group("name");
Class<?> type = typeFromSignature(matcher.group("type"));
arguments.add(argument(name, type));
arguments.add(binding(name, type));
}
return arguments;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.opencypher.gremlin.rules.GremlinServerExternalResource
import org.opencypher.gremlin.tck.GremlinQueries._
import org.opencypher.gremlin.tck.TckGremlinCypherValueConverter._
import org.opencypher.gremlin.tck.reports.CucumberReportAdapter
import org.opencypher.gremlin.traversal.ProcedureRegistry
import org.opencypher.gremlin.traversal.PredefinedProcedureRegistry
import org.opencypher.tools.tck.api._
import org.opencypher.tools.tck.values.CypherValue

Expand Down Expand Up @@ -66,7 +66,7 @@ object TinkerGraphServerEmbeddedGraph extends Graph with ProcedureSupport {

override def close(): Unit = {
tinkerGraphServerEmbedded.gremlinClient().submit(dropQuery).all().join()
ProcedureRegistry.clear()
PredefinedProcedureRegistry.clear();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
import org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer;
import org.opencypher.gremlin.traversal.CustomFunction;
import org.opencypher.gremlin.traversal.CustomPredicate;
import org.opencypher.gremlin.traversal.ProcedureRegistry;

public class CypherPlugin implements GremlinPlugin {

private static final ImportCustomizer imports = DefaultImportCustomizer.build()
.addClassImports(CustomPredicate.class)
.addMethodImports(CustomPredicate.class.getDeclaredMethods())
.addClassImports(CustomFunction.class)
.addMethodImports(CustomFunction.class.getDeclaredMethods())
.addMethodImports(getDeclaredMethod(ProcedureRegistry.class, "procedureCall", String.class))
.create();

private static Method getDeclaredMethod(Class<?> klass, String name, Class<?>... parameterTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import org.opencypher.gremlin.translation.groovy.GroovyPredicate;
import org.opencypher.gremlin.translation.translator.Translator;
import org.opencypher.gremlin.traversal.ParameterNormalizer;
import org.opencypher.gremlin.traversal.ProcedureRegistry;
import org.opencypher.gremlin.traversal.ProcedureContext;
import org.opencypher.gremlin.traversal.ReturnNormalizer;
import org.slf4j.Logger;

Expand All @@ -67,10 +67,6 @@ public class CypherOpProcessor extends AbstractEvalOpProcessor {

private static final Logger logger = getLogger(CypherOpProcessor.class);

static {
ProcedureRegistry.load();
}

public CypherOpProcessor() {
super(true);
}
Expand Down Expand Up @@ -103,7 +99,9 @@ private void evalCypher(Context context) throws OpProcessorException {
Translator<String, GroovyPredicate> stringTranslator = Translator.builder()
.gremlinGroovy()
.inlineParameters()
.procedures(ProcedureContext.global().all())
.build();

String gremlin = ast.buildTranslation(stringTranslator);
logger.info("Gremlin: {}", gremlin);

Expand All @@ -112,7 +110,11 @@ private void evalCypher(Context context) throws OpProcessorException {
return;
}

Translator<GraphTraversal, P> traversalTranslator = Translator.builder().traversal(g).build();
Translator<GraphTraversal, P> traversalTranslator = Translator.builder()
.traversal(g)
.procedures(ProcedureContext.global().all())
.build();

GraphTraversal<?, ?> traversal = ast.buildTranslation(traversalTranslator);
ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes());
Traversal<?, Map<String, Object>> normalizedTraversal = traversal.map(returnNormalizer::normalize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
*/
package org.opencypher.gremlin.extension;

public final class CypherArgument {
public final class CypherBinding {
private final String name;
private final Class<?> type;

public CypherArgument(String name, Class<?> type) {
public CypherBinding(String name, Class<?> type) {
this.name = name;
this.type = type;
}

public static CypherArgument argument(String name, Class<?> type) {
return new CypherArgument(name, type);
public static CypherBinding binding(String name, Class<?> type) {
return new CypherBinding(name, type);
}

public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,43 @@

import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
* User-defined procedure implementation.
*
* @see org.opencypher.gremlin.traversal.ProcedureRegistry
*/
@FunctionalInterface
public interface CypherProcedure {

String name();

List<CypherBinding> arguments();

List<CypherBinding> results();

List<Map<String, Object>> call(Map<String, Object> arguments);

static CypherProcedure cypherProcedure(
String name,
List<CypherBinding> arguments,
List<CypherBinding> results,
Function<Map<String, Object>, List<Map<String, Object>>> implementation) {
return new CypherProcedure() {
@Override
public String name() {
return name;
}

@Override
public List<CypherBinding> arguments() {
return arguments;
}

@Override
public List<CypherBinding> results() {
return results;
}

@Override
public List<Map<String, Object>> call(Map<String, Object> arguments) {
return implementation.apply(arguments);
}
};
}
}
Loading