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

Detects when a MemberExpression returns null and adjusts it as a ConstantExpression #38

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions Neo4jClient/Cypher/CypherWhereExpressionVisitor.cs
Expand Up @@ -216,6 +216,45 @@ void VisitParameterMember(MemberExpression node)
void VisitConstantMember(MemberExpression node)
{
var value = GetConstantExpressionValue(node);

// if the value is null, sending a parameter would return something we don't want.
// A PropertyBag within the Neo4j server cannot have property with null value, that is, having a null
// property is the same as not having the property.
var text = TextOutput.ToString();
if (value == null && text.EndsWith(NotEqual))
{
TextOutput.Remove(TextOutput.ToString().LastIndexOf(NotEqual, StringComparison.Ordinal), NotEqual.Length);
if (capabilities.SupportsNullComparisonsWithIsOperator)
{
TextOutput.Append(" is not null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic branch isn't tested.

}
else
{
TextOutput.Remove(TextOutput.ToString().LastIndexOf(lastWrittenMemberName, StringComparison.Ordinal), lastWrittenMemberName.Length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic branch isn't tested.

TextOutput.Append(string.Format("has({0})", lastWrittenMemberName));
}

// no further processing is required
return;
}

if (value == null && text.EndsWith(Equal))
{
TextOutput.Remove(TextOutput.ToString().LastIndexOf(Equal, StringComparison.Ordinal), Equal.Length);
if (capabilities.SupportsNullComparisonsWithIsOperator)
{
TextOutput.Append(" is null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic branch isn't tested.

}
else
{
TextOutput.Remove(TextOutput.ToString().LastIndexOf(lastWrittenMemberName, StringComparison.Ordinal), lastWrittenMemberName.Length);
TextOutput.Append(string.Format("not(has({0}))", lastWrittenMemberName));
}

// no further processing is required
return;
}

if (capabilities.SupportsPropertySuffixesForControllingNullComparisons && value != null)
{
SwapNullQualifierFromDefaultTrueToDefaultFalseIfTextEndsWithAny(new[]
Expand Down
5 changes: 5 additions & 0 deletions Test/Cypher/CypherFluentQueryWhereTests.cs
Expand Up @@ -16,6 +16,11 @@ class Foo
// ReSharper restore ClassNeverInstantiated.Local
// ReSharper restore UnusedAutoPropertyAccessor.Local

class MockWithNullField
{
public string NullField { get; set; }
}

[Test]
public void ComparePropertiesAcrossEntitiesEqual()
{
Expand Down
60 changes: 60 additions & 0 deletions Test/Cypher/CypherWhereExpressionBuilderTests.cs
Expand Up @@ -158,6 +158,66 @@ public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLes
Assert.AreEqual(123, parameters["p0"]);
}

[Test]
public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty()
{
var parameters = new Dictionary<string, object>();
var fooWithNulls = new Foo
{
NullableBar = null
};
Expression<Func<Foo, bool>> expression = foo => foo.NullableBar == fooWithNulls.NullableBar;

var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19);

Assert.AreEqual("(foo.NullableBar? is null)", result);
}

[Test]
public void For20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty()
{
var parameters = new Dictionary<string, object>();
var fooWithNulls = new Foo
{
NullableBar = null
};
Expression<Func<Foo, bool>> expression = foo => foo.NullableBar == fooWithNulls.NullableBar;

var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20);

Assert.AreEqual("(not(has(foo.NullableBar)))", result);
}

[Test]
public void ForPre20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty()
{
var parameters = new Dictionary<string, object>();
var fooWithNulls = new Foo
{
NullableBar = null
};
Expression<Func<Foo, bool>> expression = foo => foo.NullableBar != fooWithNulls.NullableBar;

var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19);

Assert.AreEqual("(foo.NullableBar? is not null)", result);
}

[Test]
public void For20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty()
{
var parameters = new Dictionary<string, object>();
var fooWithNulls = new Foo
{
NullableBar = null
};
Expression<Func<Foo, bool>> expression = foo => foo.NullableBar != fooWithNulls.NullableBar;

var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20);

Assert.AreEqual("(has(foo.NullableBar))", result);
}

[Test]
public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNull()
{
Expand Down