Permalink
Browse files

RavenDB-301 - supporting tertiary includes

  • Loading branch information...
1 parent 282ed69 commit 0a06b7dd5b6be1d36995edf0fd00f4379beb801a @ayende ayende committed May 22, 2012
@@ -25,6 +25,7 @@ public class QueryResult
/// </summary>
/// <value>The includes.</value>
public List<RavenJObject> Includes { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether the index is stale.
/// </summary>
@@ -356,6 +356,8 @@ public QueryResult Query(string index, IndexQuery query, string[] includes)
includeCmd.Execute(result);
}
+ includeCmd.AlsoInclude(queryResult.IdsToInclude);
+
EnsureLocalDate(queryResult.Includes);
return queryResult;
@@ -18,8 +18,18 @@ public interface IClientSideDatabase
T Load<T>(string docId);
/// <summary>
- /// Loading a document during result transformers
+ /// Loading documents during result transformers
+ /// </summary>
+ T[] Load<T>(IEnumerable<string> docIds);
+
+ /// <summary>
+ /// Will ask RavenDB to include this document in the query results
+ /// </summary>
+ object Include(string docId);
+
+ /// <summary>
+ /// Will ask RavenDB to include these documents in the query results
/// </summary>
- T[] Load<T>(IEnumerable<string> docId);
+ object Include(IEnumerable<string> docId);
}
}
@@ -0,0 +1,15 @@
+// -----------------------------------------------------------------------
+// <copyright file="QueryResultWithIncludes.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+using System.Collections.Generic;
+using Raven.Abstractions.Data;
+
+namespace Raven.Database.Data
+{
+ public class QueryResultWithIncludes : QueryResult
+ {
+ public HashSet<string> IdsToInclude { get; set; }
+ }
+}
@@ -769,14 +769,15 @@ private IndexCreationOptions FindIndexCreationOptions(IndexDefinition definition
return findIndexCreationOptions;
}
- public QueryResult Query(string index, IndexQuery query)
+ public QueryResultWithIncludes Query(string index, IndexQuery query)
{
index = IndexDefinitionStorage.FixupIndexName(index);
var list = new List<RavenJObject>();
var stale = false;
Tuple<DateTime, Guid> indexTimestamp = Tuple.Create(DateTime.MinValue, Guid.Empty);
Guid resultEtag = Guid.Empty;
var nonAuthoritativeInformation = false;
+ var idsToLoad = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
TransactionalStorage.Batch(
actions =>
{
@@ -794,7 +795,7 @@ public QueryResult Query(string index, IndexQuery query)
{
throw new IndexDisabledException(indexFailureInformation);
}
- var docRetriever = new DocumentRetriever(actions, ReadTriggers);
+ var docRetriever = new DocumentRetriever(actions, ReadTriggers, idsToLoad);
var indexDefinition = GetIndexDefinition(index);
var fieldsToFetch = new FieldsToFetch(query.FieldsToFetch, query.AggregationOperation,
viewGenerator.ReduceDefinition == null
@@ -840,9 +841,8 @@ into doc
{
throw new InvalidOperationException("The transform results function failed.\r\n" + string.Join("\r\n", transformerErrors));
}
-
});
- return new QueryResult
+ return new QueryResultWithIncludes
{
IndexName = index,
Results = list,
@@ -852,7 +852,8 @@ into doc
TotalResults = query.TotalSize.Value,
IndexTimestamp = indexTimestamp.Item1,
IndexEtag = indexTimestamp.Item2,
- ResultEtag = resultEtag
+ ResultEtag = resultEtag,
+ IdsToInclude = idsToLoad
};
}
@@ -33,11 +33,14 @@ public class DocumentRetriever : ITranslatorDatabaseAccessor
private readonly HashSet<string> loadedIdsForFilter = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
private readonly IStorageActionsAccessor actions;
private readonly OrderedPartCollection<AbstractReadTrigger> triggers;
+ private readonly HashSet<string> itemsToInclude;
- public DocumentRetriever(IStorageActionsAccessor actions, OrderedPartCollection<AbstractReadTrigger> triggers)
+ public DocumentRetriever(IStorageActionsAccessor actions, OrderedPartCollection<AbstractReadTrigger> triggers,
+ HashSet<string> itemsToInclude = null)
{
this.actions = actions;
this.triggers = triggers;
+ this.itemsToInclude = itemsToInclude ?? new HashSet<string>();
}
public JsonDocument RetrieveDocumentForQuery(IndexQueryResult queryResult, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch)
@@ -205,6 +208,39 @@ public T ProcessReadVetoes<T>(T document, TransactionInformation transactionInfo
return document;
}
+ public dynamic Include(object maybeId)
+ {
+ if (maybeId == null || maybeId is DynamicNullObject)
+ return new DynamicNullObject();
+ var id = maybeId as string;
+ if (id != null)
+ return Include(id);
+ var jId = maybeId as RavenJValue;
+ if (jId != null)
+ return Include(jId.Value.ToString());
+
+ foreach (var itemId in (IEnumerable)maybeId)
+ {
+ Include(itemId);
+ }
+ return new DynamicNullObject();
+
+ }
+ public dynamic Include(string id)
+ {
+ itemsToInclude.Add(id);
+ return new DynamicNullObject();
+ }
+
+ public dynamic Include(IEnumerable<string> ids)
+ {
+ foreach (var id in ids)
+ {
+ itemsToInclude.Add(id);
+ }
+ return new DynamicNullObject();
+ }
+
public dynamic Load(string id)
{
var document = GetDocumentWithCaching(id);
@@ -17,5 +17,10 @@ public interface ITranslatorDatabaseAccessor
/// Returns the document matching this id, if exists, or null if it doesn't
/// </summary>
dynamic Load(object maybeId);
+
+ /// <summary>
+ /// Returns null object
+ /// </summary>
+ dynamic Include(object maybeId);
}
}
@@ -11,7 +11,7 @@ namespace Raven.Database.Queries
{
public static class DynamicQueryExtensions
{
- public static QueryResult ExecuteDynamicQuery(this DocumentDatabase self, string entityName, IndexQuery indexQuery)
+ public static QueryResultWithIncludes ExecuteDynamicQuery(this DocumentDatabase self, string entityName, IndexQuery indexQuery)
{
var dynamicQueryRunner = (DynamicQueryRunner)self.ExtensionsState.GetOrAddAtomically(typeof(DynamicQueryExtensions), o => new DynamicQueryRunner(self));
return dynamicQueryRunner.ExecuteDynamicQuery(entityName, indexQuery);
@@ -29,7 +29,7 @@ public DynamicQueryRunner(DocumentDatabase database)
temporaryIndexes = new ConcurrentDictionary<string, TemporaryIndexInfo>();
}
- public QueryResult ExecuteDynamicQuery(string entityName, IndexQuery query)
+ public QueryResultWithIncludes ExecuteDynamicQuery(string entityName, IndexQuery query)
{
// Create the map
var map = DynamicQueryMapping.Create(documentDatabase, query, entityName);
@@ -71,10 +71,10 @@ private static void UpdateFieldsInArray(DynamicQueryMapping map, string[] fields
}
}
- private QueryResult ExecuteActualQuery(IndexQuery query, DynamicQueryMapping map, Tuple<string, bool> touchTemporaryIndexResult, string realQuery)
+ private QueryResultWithIncludes ExecuteActualQuery(IndexQuery query, DynamicQueryMapping map, Tuple<string, bool> touchTemporaryIndexResult, string realQuery)
{
// Perform the query until we have some results at least
- QueryResult result;
+ QueryResultWithIncludes result;
var sp = Stopwatch.StartNew();
while (true)
{
@@ -123,6 +123,7 @@
<Compile Include="Config\CertGenerator.cs" />
<Compile Include="Config\ConfigOptionDocs.cs" />
<Compile Include="Config\MemoryStatistics.cs" />
+ <Compile Include="Data\QueryResultWithIncludes.cs" />
<Compile Include="Impl\Clustering\ClusterInspecter.cs" />
<Compile Include="Impl\Clustering\ClusterResourceState.cs" />
<Compile Include="Impl\Clustering\ErrorCodes.cs" />
@@ -17,7 +17,6 @@ namespace Raven.Database.Server.Responders
{
public class AddIncludesCommand
{
-
public AddIncludesCommand(
DocumentDatabase database,
TransactionInformation transactionInformation,
@@ -32,6 +31,14 @@ public class AddIncludesCommand
LoadedIds = loadedIds;
}
+ public void AlsoInclude(IEnumerable<string> ids)
+ {
+ foreach (var id in ids)
+ {
+ LoadId(id, null);
+ }
+ }
+
private string[] Includes { get; set; }
private Action<Guid,RavenJObject> Add { get; set; }
@@ -89,26 +96,24 @@ private void ExecuteInternal(RavenJToken token, string prefix)
case JTokenType.Integer:
LoadId(token.Value<int>().ToString(CultureInfo.InvariantCulture), prefix);
break;
- default:
- // here we ignore everything else
- // if it ain't a string or array, it is invalid
- // as an id
- break;
+ // here we ignore everything else
+ // if it ain't a string or array, it is invalid
+ // as an id
}
}
private void LoadId(string value, string prefix)
{
- value = (prefix ?? string.Empty) + value;
+ value = (prefix != null ? prefix + value : value);
if (LoadedIds.Add(value) == false)
return;
var includedDoc = Database.Get(value, TransactionInformation);
- if (includedDoc != null)
- {
- Debug.Assert(includedDoc.Etag != null);
- Add(includedDoc.Etag.Value, includedDoc.ToJson());
- }
+ if (includedDoc == null)
+ return;
+
+ Debug.Assert(includedDoc.Etag != null);
+ Add(includedDoc.Etag.Value, includedDoc.ToJson());
}
}
}
@@ -11,6 +11,7 @@
using Raven.Abstractions.Data;
using Raven.Abstractions.Indexing;
using System.Linq;
+using Raven.Database.Data;
using Raven.Database.Extensions;
using Raven.Database.Queries;
using Raven.Database.Server.Abstractions;
@@ -115,7 +116,7 @@ private void GetIndexQueryRessult(IHttpContext context, string index)
{
Guid indexEtag;
- QueryResult queryResult = ExecuteQuery(context, index, out indexEtag);
+ var queryResult = ExecuteQuery(context, index, out indexEtag);
if (queryResult == null)
return;
@@ -133,7 +134,8 @@ private void GetIndexQueryRessult(IHttpContext context, string index)
{
command.Execute(result);
}
-
+ command.AlsoInclude(queryResult.IdsToInclude);
+
context.Response.AddHeader("ETag", indexEtag.ToString());
if(queryResult.NonAuthoritativeInformation)
context.SetStatusToNonAuthoritativeInformation();
@@ -157,7 +159,7 @@ private void GetIndexDefinition(IHttpContext context, string index)
});
}
- private QueryResult ExecuteQuery(IHttpContext context, string index, out Guid indexEtag)
+ private QueryResultWithIncludes ExecuteQuery(IHttpContext context, string index, out Guid indexEtag)
{
var indexQuery = context.GetIndexQueryFromHttpContext(Database.Configuration.MaxPageSize);
@@ -188,7 +190,7 @@ private QueryResult ExecuteQuery(IHttpContext context, string index, out Guid in
return result;
}
- private QueryResult PerformQueryAgainstExistingIndex(IHttpContext context, string index, IndexQuery indexQuery, out Guid indexEtag)
+ private QueryResultWithIncludes PerformQueryAgainstExistingIndex(IHttpContext context, string index, IndexQuery indexQuery, out Guid indexEtag)
{
indexEtag = Database.GetIndexEtag(index, null);
if (context.MatchEtag(indexEtag))
@@ -202,7 +204,7 @@ private QueryResult PerformQueryAgainstExistingIndex(IHttpContext context, strin
return queryResult;
}
- private QueryResult PerformQueryAgainstDynamicIndex(IHttpContext context, string index, IndexQuery indexQuery, out Guid indexEtag)
+ private QueryResultWithIncludes PerformQueryAgainstDynamicIndex(IHttpContext context, string index, IndexQuery indexQuery, out Guid indexEtag)
{
string entityName = null;
if (index.StartsWith("dynamic/", StringComparison.InvariantCultureIgnoreCase))
@@ -0,0 +1,73 @@
+// -----------------------------------------------------------------------
+// <copyright file="RavenDB_301.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+using System.Linq;
+using Raven.Client.Document;
+using Raven.Client.Indexes;
+using Xunit;
+
+namespace Raven.Tests.Issues
+{
+ public class RavenDB_301 : RavenTest
+ {
+ [Fact]
+ public void CanUseTertiaryIncludes()
+ {
+ using(GetNewServer())
+ using(var store = new DocumentStore
+ {
+ Url = "http://localhost:8079"
+ }.Initialize())
+ {
+ new Index().Execute(store);
+ using(var session = store.OpenSession())
+ {
+ session.Store(new Item
+ {
+ Name = "Oren",
+ Parent = null
+ });
+ session.Store(new Item
+ {
+ Name = "Ayende",
+ Parent = "items/1"
+ });
+ session.SaveChanges();
+ }
+
+ using (var session = store.OpenSession())
+ {
+ var a = session.Query<Item, Index>()
+ .Customize(x => x.WaitForNonStaleResults())
+ .Single(x => x.Name == "Ayende");
+
+ session.Load<Item>(a.Parent);
+
+ Assert.Equal(1, session.Advanced.NumberOfRequests);
+ }
+ }
+ }
+
+ public class Item
+ {
+ public string Name { get; set; }
+ public string Id { get; set; }
+ public string Parent { get; set; }
+ }
+
+ public class Index : AbstractIndexCreationTask<Item>
+ {
+ public Index()
+ {
+ Map = items => from item in items
+ select new {item.Name};
+ TransformResults = (database, items) =>
+ from item in items
+ let _ = database.Include(item.Parent)
+ select item;
+ }
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 0a06b7d

Please sign in to comment.