jamesfer / cypher-query-builder

An flexible and intuitive query builder for Neo4j and Cypher.
http://jamesfer.me/cypher-query-builder/index.html
MIT License
104 stars 22 forks source link

Comparison with periods in name breaks param naming #184

Open jhanggi opened 2 years ago

jhanggi commented 2 years ago

This was a tricky one. I'm not sure if it's intended usage, but we've taken advantage of a neat little quirk, using the keys of where clause objects to do computations in the left side of the comparator.

const lastName = 'FromInput';
new Query().matchNode('n', 'Node')
  .where({
    'split(n.name, " ")': myVariable,
  })

However, this breaks if we try to split by periods because of the split('.') in compare(). It gets added to the bag as an empty string. Then we get a syntax error when we run the query because the $ param prefix is just hanging there.

Here is a failing test case:

  it("can perform a comparision when there is a dot in the key", () => {
    const clause = equals("value")(bag, "split(node.ID, '.')[0]");
    expect(clause).to.equal(`split(node.ID, '.')[0] = $something`);
    expect(bag.getParams()).to.have.property("something").that.equals("value");
  });
---
AssertionError: expected 'split(node.ID, \'.\')[0] = $' to equal 'split(node.ID, \'.\')[0] = $something'
      + expected - actual

      -split(node.ID, '.')[0] = $
      +split(node.ID, '.')[0] = $something

The ')[0] goes into the bag and gets sent through uniqueString where it gets slimmed down to just 0, then because it's a trailing number, it gets stripped off leaving us with an empty string.

One option might be to provide a fallback name if the given string gets stripped down to the empty string. Another might be to allow another argument in some of the comparisons, e.g. equals(value, false, 'myParamName').

I'm not sure if the usage of keys like this was ever intended, but it works really nicely for us. We've worked around this particular issue by creating a custom comparator, but it would be nice if the library could handle it.

I'd be happy to put together a PR, but I don't know what the best solution would be. A fallback might be the simplest, or make it so that if you have empty strings, they get incremented $0, $1, etc (assuming that's valid in cypher).

export function equalsForceParamName(
  value: any,
  paramName: string,
): Comparator {
  return (params, name) => `${name} = ${params.addParam(value, paramName)}`;
}