Skip to content

Commit

Permalink
Allowing to query on references
Browse files Browse the repository at this point in the history
  • Loading branch information
ayende committed Oct 19, 2011
1 parent 624f1a8 commit c686acd
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 20 deletions.
5 changes: 4 additions & 1 deletion Raven.Abstractions/Json/JsonDynamicConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
return val.Value;
var array = token as RavenJArray;
if (array != null)
return new DynamicJsonObject.DynamicList(array.Select(DynamicJsonObject.TransformToValue).ToArray());
{
var dynamicJsonObject = new DynamicJsonObject(new RavenJObject());
return new DynamicJsonObject.DynamicList(array.Select(dynamicJsonObject.TransformToValue).ToArray());
}

var typeName = token.Value<string>("$type");
if(typeName != null)
Expand Down
131 changes: 112 additions & 19 deletions Raven.Abstractions/Linq/DynamicJsonObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface IDynamicJsonObject
/// </summary>
public class DynamicJsonObject : DynamicObject, IEnumerable<object>, IDynamicJsonObject
{
private DynamicJsonObject parent;

public IEnumerator<object> GetEnumerator()
{
return
Expand Down Expand Up @@ -96,6 +98,13 @@ public DynamicJsonObject(RavenJObject inner)
this.inner = inner;
}


private DynamicJsonObject(DynamicJsonObject parent, RavenJObject inner)
{
this.parent = parent;
this.inner = inner;
}

/// <summary>
/// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
/// </summary>
Expand Down Expand Up @@ -127,7 +136,7 @@ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out ob
return true;
}

public static object TransformToValue(RavenJToken jToken)
public object TransformToValue(RavenJToken jToken)
{
switch (jToken.Type)
{
Expand All @@ -136,12 +145,19 @@ public static object TransformToValue(RavenJToken jToken)
var values = jObject.Value<RavenJArray>("$values");
if (values != null)
{
return new DynamicList(values.Select(TransformToValue).ToArray());
return new DynamicList(this, values.Select(TransformToValue).ToArray());
}
var refId = jObject.Value<string>("$ref");
if(refId != null)
{
var ravenJObject = FindReference(refId);
if (ravenJObject != null)
return new DynamicJsonObject(this, ravenJObject);
}
return new DynamicJsonObject(jObject);
return new DynamicJsonObject(this, jObject);
case JTokenType.Array:
var ar = jToken as RavenJArray; // cannot result in null because jToken.Type is set to Array
return new DynamicList(ar.Select(TransformToValue).ToArray());
var ar = (RavenJArray) jToken;
return new DynamicList(this, ar.Select(TransformToValue).ToArray());
case JTokenType.Date:
return jToken.Value<DateTime>();
case JTokenType.Null:
Expand All @@ -165,6 +181,63 @@ public static object TransformToValue(RavenJToken jToken)
}
}

private RavenJObject FindReference(string refId)
{
var p = this;
while (p.parent != null)
p = p.parent;

return p.Scan().FirstOrDefault(x => x.Value<string>("$id") == refId);
}

private IEnumerable<RavenJObject> Scan()
{
var objs = new List<RavenJObject>();
var lists = new List<RavenJArray>();

objs.Add(inner);

while (objs.Count > 0 || lists.Count > 0)
{
var objCopy = objs;
objs = new List<RavenJObject>();
foreach (var obj in objCopy)
{
yield return obj;
foreach (var property in obj.Properties)
{
switch (property.Value.Type)
{
case JTokenType.Object:
objs.Add((RavenJObject)property.Value);
break;
case JTokenType.Array:
lists.Add((RavenJArray)property.Value);
break;
}
}
}

var listsCopy = lists;
lists = new List<RavenJArray>();
foreach (var list in listsCopy)
{
foreach (var item in list)
{
switch (item.Type)
{
case JTokenType.Object:
objs.Add((RavenJObject)item);
break;
case JTokenType.Array:
lists.Add((RavenJArray)item);
break;
}
}
}
}
}

/// <summary>
/// Gets the value for the specified name
/// </summary>
Expand Down Expand Up @@ -219,6 +292,7 @@ public object GetDocumentId()
/// </summary>
public class DynamicList : DynamicObject, IEnumerable<object>
{
private readonly DynamicJsonObject parent;
private readonly object[] inner;

/// <summary>
Expand All @@ -230,6 +304,11 @@ public DynamicList(object[] inner)
this.inner = inner;
}

internal DynamicList(DynamicJsonObject parent, object[] inner):this(inner)
{
this.parent = parent;
}

/// <summary>
/// Provides the implementation for operations that invoke a member. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as calling a method.
/// </summary>
Expand Down Expand Up @@ -258,29 +337,43 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
return base.TryInvokeMember(binder, args, out result);
}

private IEnumerable<dynamic> Enumerate()
{
foreach (var item in inner)
{
var ravenJObject = item as RavenJObject;
if(ravenJObject != null)
yield return new DynamicJsonObject(parent, ravenJObject);
var ravenJArray = item as RavenJArray;
if (ravenJArray != null)
yield return new DynamicList(parent, ravenJArray.ToArray());
yield return item;
}
}

public dynamic First(Func<dynamic, bool> predicate)
{
return inner.First(predicate);
return Enumerate().First(predicate);
}

public dynamic FirstOrDefault(Func<dynamic, bool> predicate)
{
return inner.FirstOrDefault(predicate);
return Enumerate().FirstOrDefault(predicate);
}

public dynamic Single(Func<dynamic, bool> predicate)
{
return inner.Single(predicate);
return Enumerate().Single(predicate);
}

public IEnumerable<dynamic> Distinct()
{
return new DynamicList(inner.Distinct().ToArray());
return new DynamicList(Enumerate().Distinct().ToArray());
}

public dynamic SingleOrDefault(Func<dynamic, bool> predicate)
{
return inner.SingleOrDefault(predicate);
return Enumerate().SingleOrDefault(predicate);
}

/// <summary>
Expand All @@ -289,12 +382,12 @@ public dynamic SingleOrDefault(Func<dynamic, bool> predicate)
/// <returns></returns>
public IEnumerator<object> GetEnumerator()
{
return ((IEnumerable<object>)inner).GetEnumerator();
return Enumerate().GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)inner).GetEnumerator();
return Enumerate().GetEnumerator();
}

/// <summary>
Expand Down Expand Up @@ -404,39 +497,39 @@ public int Count
/// </summary>
public int Sum(Func<dynamic, int> aggregator)
{
return inner.Sum(aggregator);
return Enumerate().Sum(aggregator);
}

/// <summary>
/// Redirector for sum operation
/// </summary>
public decimal Sum(Func<dynamic, decimal> aggregator)
{
return inner.Sum(aggregator);
return Enumerate().Sum(aggregator);
}

/// <summary>
/// Redirector for sum operation
/// </summary>
public float Sum(Func<dynamic, float> aggregator)
{
return inner.Sum(aggregator);
return Enumerate().Sum(aggregator);
}

/// <summary>
/// Redirector for sum operation
/// </summary>
public double Sum(Func<dynamic, double> aggregator)
{
return inner.Sum(aggregator);
return Enumerate().Sum(aggregator);
}

/// <summary>
/// Redirector for sum operation
/// </summary>
public long Sum(Func<dynamic, long> aggregator)
{
return inner.Sum(aggregator);
return Enumerate().Sum(aggregator);
}

/// <summary>
Expand All @@ -450,12 +543,12 @@ public int Length

public IEnumerable<object> Select(Func<object, object> func)
{
return new DynamicList(inner.Select(func).ToArray());
return new DynamicList(parent, inner.Select(func).ToArray());
}

public IEnumerable<object> SelectMany(Func<object, IEnumerable<object>> func)
{
return new DynamicList(inner.SelectMany(func).ToArray());
return new DynamicList(parent, inner.SelectMany(func).ToArray());
}
}

Expand Down
83 changes: 83 additions & 0 deletions Raven.Tests/Bugs/JsonReferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Raven.Client.Embedded;
using Xunit;

namespace Raven.Tests.Bugs
{
public class JsonReferences
{
[Fact]
public void can_index_on_a_reference2()
{
using (var store = new EmbeddableDocumentStore
{
RunInMemory = true
})
{

store.Initialize();
using (var session = store.OpenSession())
{
var category = new Category()
{
Name = "Parent"
};

category.Add(new Category()
{
Name = "Child"
});

session.Store(category);
session.SaveChanges();
}

using (var session = store.OpenSession())
{
var results0 = session.Query<Category>()
.Customize(x=>x.WaitForNonStaleResults(TimeSpan.FromHours(1)))
.ToList();
Assert.Equal(1, results0.Count);

// WORKS
var results1 = session.Query<Category>()
.Customize(x => x.WaitForNonStaleResults())
.Where(x => x.Children.Any(y => y.Name == "Child")).
ToList();
Assert.Equal(1, results1.Count);

// FAILS
var results2 = session.Query<Category>()
.Customize(x => x.WaitForNonStaleResults())
.Where(x => x.Children.Any(y => y.Parent.Name == "Parent"))
.ToList();
Assert.Equal(1, results2.Count);
}
}
}

[JsonObject(IsReference = true)]
public class Category
{
public string Id { get; set; }
public string Name { get; set; }
public Category Parent { get; set; }
public List<Category> Children { get; set; }

public Category()
{
Children = new List<Category>();
}

public void Add(Category category)
{
category.Parent = this;
Children.Add(category);
}
}
}

}
1 change: 1 addition & 0 deletions Raven.Tests/Raven.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
<Compile Include="Bugs\Indexing\CanHaveAnIndexNameThatStartsWithDynamic.cs" />
<Compile Include="Bugs\Indexing\InvalidIndexes.cs" />
<Compile Include="Bugs\Issue355.cs" />
<Compile Include="Bugs\JsonReferences.cs" />
<Compile Include="Bugs\LarsErik.cs" />
<Compile Include="Bugs\Iulian\GeneratesCorrectTemporaryIndex.cs" />
<Compile Include="Bugs\LiveProjections\Entities\Place.cs" />
Expand Down

0 comments on commit c686acd

Please sign in to comment.