Skip to content

Commit

Permalink
Merge pull request #133 from Readify/nullable-with
Browse files Browse the repository at this point in the history
Respect Cypher capability detection for null suffixes in WHERE clauses
  • Loading branch information
tathamoddie committed Nov 30, 2015
2 parents c801ff1 + b666591 commit f179f57
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 19 deletions.
32 changes: 31 additions & 1 deletion Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs
Expand Up @@ -78,6 +78,36 @@ public void ShouldReturnSpecificPropertyOnItsOwn()
Assert.AreEqual("WITH a.Name", query.QueryText);
}

[Test]
public void ShouldReturnSpecificPropertyWithAliasWithNullableSuffixInCypher19()
{
var client = Substitute.For<IRawGraphClient>();
client.CypherCapabilities.Returns(CypherCapabilities.Cypher19);
var query = new CypherFluentQuery(client)
.With(a => new
{
SomeAlias = a.As<Commodity>().Name
})
.Query;

Assert.AreEqual("WITH a.Name? AS SomeAlias", query.QueryText);
}

[Test]
public void ShouldReturnSpecificPropertyWithAliasWithoutNullableSuffixInCypher20()
{
var client = Substitute.For<IRawGraphClient>();
client.CypherCapabilities.Returns(CypherCapabilities.Cypher20);
var query = new CypherFluentQuery(client)
.With(a => new
{
SomeAlias = a.As<Commodity>().Name
})
.Query;

Assert.AreEqual("WITH a.Name AS SomeAlias", query.QueryText);
}

[Test]
public void ShouldReturnSpecificPropertyOnItsOwnCamelAs()
{
Expand All @@ -87,7 +117,7 @@ public void ShouldReturnSpecificPropertyOnItsOwnCamelAs()
.With(a => new Commodity(){ Name = a.As<Commodity>().Name})
.Query;

Assert.AreEqual("WITH a.name? AS Name", query.QueryText);
Assert.AreEqual("WITH a.name AS Name", query.QueryText);
}

[Test]
Expand Down
3 changes: 2 additions & 1 deletion Neo4jClient/Cypher/CypherFluentQuery`With.cs
Expand Up @@ -12,7 +12,8 @@ public ICypherFluentQuery With(string withText)

ICypherFluentQuery<TResult> With<TResult>(LambdaExpression expression)
{
var withExpression = CypherWithExpressionBuilder.BuildText(expression, CamelCaseProperties);
var expressionBuilder = new CypherWithExpressionBuilder(Client.CypherCapabilities, CamelCaseProperties);
var withExpression = expressionBuilder.BuildText(expression);

return Mutate<TResult>(w =>
{
Expand Down
46 changes: 29 additions & 17 deletions Neo4jClient/Cypher/CypherWithExpressionBuilder.cs
Expand Up @@ -22,7 +22,16 @@ public class CypherWithExpressionBuilder
// - a "statement" is something like "x.Foo? AS Bar"
// - "text" is a collection of statements, like "x.Foo? AS Bar, y.Baz as Qak"

public static ReturnExpression BuildText(LambdaExpression expression, bool camelCaseProperties)
private readonly CypherCapabilities capabilities;
private readonly bool camelCaseProperties;

public CypherWithExpressionBuilder(CypherCapabilities capabilities, bool camelCaseProperties)
{
this.capabilities = capabilities ?? CypherCapabilities.Default;
this.camelCaseProperties = camelCaseProperties;
}

public ReturnExpression BuildText(LambdaExpression expression)
{
var body = expression.Body;

Expand All @@ -37,19 +46,19 @@ public static ReturnExpression BuildText(LambdaExpression expression, bool camel
{
case ExpressionType.MemberInit:
var memberInitExpression = (MemberInitExpression) body;
text = BuildText(memberInitExpression, camelCaseProperties);
text = BuildText(memberInitExpression);
return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection};
case ExpressionType.New:
var newExpression = (NewExpression) body;
text = BuildText(newExpression,camelCaseProperties);
text = BuildText(newExpression);
return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection};
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression) body;
text = BuildText(methodCallExpression);
return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Set};
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) body;
text = BuildText(memberExpression, camelCaseProperties);
text = BuildText(memberExpression);
return new ReturnExpression { Text = text, ResultMode = CypherResultMode.Set };
default:
throw new ArgumentException(WithExpressionShouldBeOneOfExceptionMessage, "expression");
Expand All @@ -66,7 +75,7 @@ public static ReturnExpression BuildText(LambdaExpression expression, bool camel
///
/// <see cref="BuildText(NewExpression)"/> caters to anonymous types.
/// </remarks>
static string BuildText(MemberInitExpression expression,bool camelCaseProperties)
string BuildText(MemberInitExpression expression)
{
if (expression.NewExpression.Constructor.GetParameters().Any())
throw new ArgumentException(
Expand All @@ -79,7 +88,7 @@ static string BuildText(MemberInitExpression expression,bool camelCaseProperties
throw new ArgumentException("All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }", "expression");
var memberAssignment = (MemberAssignment)binding;
return BuildStatement(memberAssignment.Expression, binding.Member, camelCaseProperties);
return BuildStatement(memberAssignment.Expression, binding.Member);
});

return string.Join(", ", bindingTexts.ToArray());
Expand All @@ -98,7 +107,7 @@ static string BuildText(MemberInitExpression expression,bool camelCaseProperties
///
/// This is the scenario that this build method caters for.
/// </remarks>
static string BuildText(NewExpression expression, bool camelCaseProperties)
string BuildText(NewExpression expression)
{
var resultingType = expression.Constructor.DeclaringType;
Debug.Assert(resultingType != null, "resultingType != null");
Expand All @@ -117,7 +126,7 @@ static string BuildText(NewExpression expression, bool camelCaseProperties)
var bindingTexts = expression.Members.Select((member, index) =>
{
var argument = expression.Arguments[index];
return BuildStatement(argument, member,camelCaseProperties);
return BuildStatement(argument, member);
});

return string.Join(", ", bindingTexts.ToArray());
Expand All @@ -126,15 +135,15 @@ static string BuildText(NewExpression expression, bool camelCaseProperties)
/// <remarks>
/// This build method caters to expressions like: <code>item => item.Count()</code>
/// </remarks>
static string BuildText(MethodCallExpression expression)
string BuildText(MethodCallExpression expression)
{
return BuildStatement(expression, false);
}

/// <remarks>
/// This build method caters to expressions like: <code>item => item.As&lt;Foo&gt;().Bar</code>
/// </remarks>
static string BuildText(MemberExpression expression, bool camelCaseProperties)
string BuildText(MemberExpression expression)
{
var innerExpression = expression.Expression as MethodCallExpression;
if (innerExpression == null ||
Expand All @@ -148,13 +157,13 @@ static string BuildText(MemberExpression expression, bool camelCaseProperties)
return statement;
}

static string BuildStatement(Expression sourceExpression, MemberInfo targetMember, bool camelCaseProperties)
string BuildStatement(Expression sourceExpression, MemberInfo targetMember)
{
var unwrappedExpression = UnwrapImplicitCasts(sourceExpression);

var memberExpression = unwrappedExpression as MemberExpression;
if (memberExpression != null)
return BuildStatement(memberExpression, targetMember,camelCaseProperties);
return BuildStatement(memberExpression, targetMember);

var methodCallExpression = unwrappedExpression as MethodCallExpression;
if (methodCallExpression != null)
Expand All @@ -173,7 +182,7 @@ static string BuildStatement(Expression sourceExpression, MemberInfo targetMembe
unwrappedExpression.GetType().FullName));
}

static string BuildStatement(MemberExpression memberExpression, MemberInfo targetMember, bool camelCaseProperties)
string BuildStatement(MemberExpression memberExpression, MemberInfo targetMember)
{
MethodCallExpression methodCallExpression;
MemberInfo memberInfo;
Expand All @@ -198,10 +207,13 @@ static string BuildStatement(MemberExpression memberExpression, MemberInfo targe
throw new InvalidOperationException(
"Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");

var isTargetMemberNullable = IsMemberNullable(targetMember);
var isNullable = isTargetMemberNullable || IsMemberNullable(memberInfo);

var optionalIndicator = isNullable ? "?" : "";
var optionalIndicator = string.Empty;
if (capabilities.SupportsPropertySuffixesForControllingNullComparisons)
{
var isTargetMemberNullable = IsMemberNullable(targetMember);
var isNullable = isTargetMemberNullable || IsMemberNullable(memberInfo);
if (isNullable) optionalIndicator = "?";
}

return string.Format("{0}.{1}{2} AS {3}", targetObject.Name, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name), optionalIndicator, targetMember.Name);
}
Expand Down

0 comments on commit f179f57

Please sign in to comment.