bkrypt
bkrypt

Reputation: 103

Gremlin DSL usage errors within `repeat` step

We are using gremlin-javascript and have recently started to define a DSL to simplify our queries.

I am not sure if I've overlooked some caveat, but when attempting to use DSL methods within a repeat step, I consistently receive (...).someDslFunction is not a function errors, but using the same DSL function outside of repeat works without issue.

Here is a short (contrived) DSL definition that produces this issue:

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

And here are two uses of it, the first works without issue, the second causes the __.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: Thanks @stephen for pointing out the now so obvious issue:

I had redefined callOnEmptyTraversal for use with our DSL, and foolishly destructured the standard TinkerPop anonymous traversals into our custom ones. These obviously are calling the original callOnEmptyTraversal which does indeed use 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 // Whoops
};

const __ = statics;

SOLUTION: Just in case anyone else runs into this scenario. This is how I solved the issue of merging our DSL anonymous traversal spawns with the standard TinkerPop ones:

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;

Upvotes: 2

Views: 288

Answers (1)

stephen mallette
stephen mallette

Reputation: 46216

I assume that the problem is that it's because you start your traversal with __ which is the standard TinkerPop spawn for anonymous traversals. As a result you get a GraphTraversal created rather than your CustomDSLTraversalSource. The TinkerPop gremlin-javascript documentation states that:

steps that are made available on a GraphTraversal should also be made available as spawns for anonymous traversals

So you probably should have your own version of __ that returns the CustomDSLTraversalSource. If you want to see more explicitly where things are going wrong, see in the code that callOnEmptyTraversal() returns GraphTraversal and obviously your DSL methods won't be available on that class.

Upvotes: 1

Related Questions