Our team is utilizing gremlin-javascript
and has recently implemented a DSL (Domain Specific Language) to streamline our queries.
However, we have encountered an issue when trying to use DSL methods within a repeat
step. Every time we attempt this, we consistently get "
(...).someDslFunction is not a function
" errors. Interestingly, using the same DSL function outside of the repeat
block works perfectly fine.
Below is a simplified DSL definition that showcases this problem:
class CustomDSLTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
hasNotLabel(...args) {
return this.not(__.hasLabel(...args));
}
filterNotLabel(...args) {
return this.filter(__.hasNotLabel(...args));
}
}
class CustomDSLTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, CustomDSLTraversalSource, CustomDSLTraversal);
}
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
...gremlin.process.statics
};
const __ = statics;
const g = traversal(CustomDSLTraversalSource).withRemote(connection);
Here are two instances where this issue arises - the first one functions as expected, while the second triggers "
__.outE().(...).filterNotLabel is not a function
" error.
g.V('foo').outE().filterNotLabel('x', 'y').otherV(); // No errors
g.V('foo').repeat(__.outE().filterNotLabel('x', 'y').otherV()).times(1); // Error
// __.outE(...).filterNotLabel is not a function
EDIT: A member of our team pointed out a possible solution:
The issue stemmed from redefining callOnEmptyTraversal
for usage with our DSL, while mistakenly integrating standard TinkerPop anonymous traversals into our custom ones. As a result, the original callOnEmptyTraversal
was employed, which utilizes an instance of the base GraphTraversal
.
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
mapToObject: (...args) => callOnEmptyTraversal('mapToObject', args),
...gremlin.process.statics // Oops
};
const __ = statics;
SOLUTION: To address this issue and merge our DSL anonymous traversal spawns with the standard TinkerPop ones, I made the following adjustment:
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
function mapToCallOnEmptyTraversal(s, fn) {
s[fn] = (...args) => callOnEmptyTraversal(fn, args);
return s;
}
const statics = ['hasNotLabel', 'mapToObject']
.concat(Object.keys(gremlin.process.statics))
.reduce(mapToCallOnEmptyTraversal, {});
const __ = statics;