Skip to content
Browse files

Add a helper method to dynamically store fields in an index, without …

…explicitly using the Lucene.Net.Document.Field class.
  • Loading branch information...
1 parent 6302147 commit 2d86d1ea1240ef8ef78feadb3f64c139d5727d1a @fitzchak fitzchak committed Jan 22, 2012
View
16 Raven.Client.Lightweight/Indexes/AbstractIndexCreationTask.cs
@@ -49,6 +49,22 @@ public abstract class AbstractIndexCreationTask
/// <value>The document store.</value>
public DocumentConvention Conventions { get; set; }
+ /// <summary>
+ /// Provide a way to dynamically index values with runtime known values
+ /// </summary>
+ protected object CreateField(string name, object value, bool stored, bool indexed)
+ {
+ throw new NotSupportedException("This can only be run on the server side");
+ }
+
+ /// <summary>
+ /// Provide a way to dynamically index values with runtime known values
+ /// </summary>
+ protected object CreateField(string name, object value)
+ {
+ throw new NotSupportedException("This can only be run on the server side");
+ }
+
#if !NET_3_5
/// <summary>
/// Allows to use lambdas recursively
View
47 Raven.Database/Indexing/AnonymousObjectToLuceneDocumentConverter.cs
@@ -34,23 +34,23 @@ public AnonymousObjectToLuceneDocumentConverter(IndexDefinition indexDefinition)
this.indexDefinition = indexDefinition;
}
- public IEnumerable<AbstractField> Index(object val, PropertyDescriptorCollection properties, IndexDefinition indexDefinition, Field.Store defaultStorage)
+ public IEnumerable<AbstractField> Index(object val, PropertyDescriptorCollection properties, Field.Store defaultStorage)
{
return (from property in properties.Cast<PropertyDescriptor>()
let name = property.Name
where name != Constants.DocumentIdFieldName
let value = property.GetValue(val)
- from field in CreateFields(name, value, indexDefinition, defaultStorage)
+ from field in CreateFields(name, value, defaultStorage)
select field);
}
- public IEnumerable<AbstractField> Index(RavenJObject document, IndexDefinition indexDefinition, Field.Store defaultStorage)
+ public IEnumerable<AbstractField> Index(RavenJObject document, Field.Store defaultStorage)
{
return (from property in document
let name = property.Key
where name != Constants.DocumentIdFieldName
let value = GetPropertyValue(property.Value)
- from field in CreateFields(name, value, indexDefinition, defaultStorage)
+ from field in CreateFields(name, value, defaultStorage)
select field);
}
@@ -77,7 +77,7 @@ private static object GetPropertyValue(RavenJToken property)
/// 1. with the supplied name, containing the numeric value as an unanalyzed string - useful for direct queries
/// 2. with the name: name +'_Range', containing the numeric value in a form that allows range queries
/// </summary>
- private IEnumerable<AbstractField> CreateFields(string name, object value, IndexDefinition indexDefinition, Field.Store defaultStorage)
+ public IEnumerable<AbstractField> CreateFields(string name, object value, Field.Store defaultStorage)
{
if(string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Field must be not null, not empty and cannot contain whitespace", "name");
@@ -88,31 +88,32 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
name = "_" + name;
}
+ var storage = indexDefinition.GetStorage(name, defaultStorage);
if (value == null)
{
- yield return CreateFieldWithCaching(name, Constants.NullValue, indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, Constants.NullValue, storage,
Field.Index.NOT_ANALYZED_NO_NORMS);
yield break;
}
if(Equals(value, string.Empty))
{
- yield return CreateFieldWithCaching(name, Constants.EmptyString, indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, Constants.EmptyString, storage,
Field.Index.NOT_ANALYZED_NO_NORMS);
yield break;
}
if (value is DynamicNullObject)
{
if(((DynamicNullObject)value ).IsExplicitNull)
{
- yield return CreateFieldWithCaching(name, Constants.NullValue, indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, Constants.NullValue, storage,
Field.Index.NOT_ANALYZED_NO_NORMS);
}
yield break;
}
if(value is BoostedValue)
{
var boostedValue = (BoostedValue)value;
- foreach (var field in CreateFields(name, boostedValue.Value, indexDefinition, defaultStorage))
+ foreach (var field in CreateFields(name, boostedValue.Value, storage))
{
field.SetBoost(boostedValue.Boost);
field.SetOmitNorms(false);
@@ -128,7 +129,7 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
}
if(value is byte[])
{
- yield return CreateBinaryFieldWithCaching(name, (byte[])value, indexDefinition.GetStorage(name, defaultStorage));
+ yield return CreateBinaryFieldWithCaching(name, (byte[])value, storage);
yield break;
}
@@ -140,7 +141,7 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
foreach (var itemToIndex in itemsToIndex)
{
multipleItemsSameFieldCount.Add(count++);
- foreach (var field in CreateFields(name, itemToIndex, indexDefinition, defaultStorage))
+ foreach (var field in CreateFields(name, itemToIndex, storage))
{
yield return field;
}
@@ -156,18 +157,18 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
{
var val = (DateTime) value;
var postFix = val.Kind == DateTimeKind.Utc ? "Z" : "";
- yield return CreateFieldWithCaching(name, val.ToString(Default.DateTimeFormatsToWrite) + postFix, indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, val.ToString(Default.DateTimeFormatsToWrite) + postFix, storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else if(value is DateTimeOffset)
{
var val = (DateTimeOffset)value;
- yield return CreateFieldWithCaching(name, val.ToString(Default.DateTimeOffsetFormatsToWrite), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, val.ToString(Default.DateTimeOffsetFormatsToWrite), storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else
{
- yield return CreateFieldWithCaching(name, value.ToString(), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, value.ToString(), storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
yield break;
@@ -176,51 +177,51 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
if (value is string)
{
var index = indexDefinition.GetIndex(name, Field.Index.ANALYZED);
- yield return CreateFieldWithCaching(name, value.ToString(), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, value.ToString(), storage,
index);
yield break;
}
if (value is DateTime)
{
yield return CreateFieldWithCaching(name, DateTools.DateToString((DateTime)value, DateTools.Resolution.MILLISECOND),
- indexDefinition.GetStorage(name, defaultStorage),
+ storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else if (value is DateTimeOffset)
{
yield return CreateFieldWithCaching(name, DateTools.DateToString(((DateTimeOffset)value).UtcDateTime, DateTools.Resolution.MILLISECOND),
- indexDefinition.GetStorage(name, defaultStorage),
+ storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else if(value is bool)
{
- yield return new Field(name, ((bool) value) ? "true" : "false", indexDefinition.GetStorage(name, defaultStorage),
+ yield return new Field(name, ((bool) value) ? "true" : "false", storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else if(value is IConvertible) // we need this to store numbers in invariant format, so JSON could read them
{
var convert = ((IConvertible) value);
- yield return CreateFieldWithCaching(name, convert.ToString(CultureInfo.InvariantCulture), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, convert.ToString(CultureInfo.InvariantCulture), storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else if (value is IDynamicJsonObject)
{
var inner = ((IDynamicJsonObject)value).Inner;
yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
- yield return CreateFieldWithCaching(name, inner.ToString(), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, inner.ToString(), storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
else
{
yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
- yield return CreateFieldWithCaching(name, RavenJToken.FromObject(value).ToString(), indexDefinition.GetStorage(name, defaultStorage),
+ yield return CreateFieldWithCaching(name, RavenJToken.FromObject(value).ToString(), storage,
indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS));
}
- foreach (var numericField in CreateNumericFieldWithCaching(name, value, defaultStorage))
+ foreach (var numericField in CreateNumericFieldWithCaching(name, value, storage))
yield return numericField;
}
@@ -273,7 +274,7 @@ private IEnumerable<AbstractField> CreateFields(string name, object value, Index
}
}
- private static bool ShouldTreatAsEnumerable(IEnumerable itemsToIndex)
+ private static bool ShouldTreatAsEnumerable(object itemsToIndex)
{
if (itemsToIndex == null)
return false;
View
8 Raven.Database/Indexing/MapReduceIndex.cs
@@ -108,7 +108,7 @@ public override bool IsMapReduce
// we don't use the usual GroupBy, because that isn't streaming
// we rely on the fact that all values from the same docs are always outputed at
// the same time, so we can take advantage of this fact
- private static IEnumerable<IGrouping<object ,dynamic>> GroupByDocumentId(IEnumerable<object> docs)
+ private static IEnumerable<IGrouping<object, dynamic>> GroupByDocumentId(IEnumerable<object> docs)
{
var enumerator = docs.GetEnumerator();
if(enumerator.MoveNext()==false)
@@ -357,13 +357,13 @@ private IEnumerable<AbstractField> GetFields(AnonymousObjectToLuceneDocumentConv
IEnumerable<AbstractField> fields;
if (doc is IDynamicJsonObject)
{
- fields = anonymousObjectToLuceneDocumentConverter.Index(((IDynamicJsonObject)doc).Inner,
- indexDefinition, Field.Store.YES);
+
+ fields = anonymousObjectToLuceneDocumentConverter.Index(((IDynamicJsonObject)doc).Inner, Field.Store.YES);
}
else
{
properties = properties ?? TypeDescriptor.GetProperties(doc);
- fields = anonymousObjectToLuceneDocumentConverter.Index(doc, properties, indexDefinition, Field.Store.YES);
+ fields = anonymousObjectToLuceneDocumentConverter.Index(doc, properties, Field.Store.YES);
}
if (Math.Abs(boost - 1) > float.Epsilon)
{
View
5 Raven.Database/Indexing/SimpleIndex.cs
@@ -161,8 +161,7 @@ private IndexingResult ExtractIndexDataFromDocument(AnonymousObjectToLuceneDocum
var newDocId = dynamicJsonObject.GetDocumentId();
return new IndexingResult
{
- Fields = anonymousObjectToLuceneDocumentConverter.Index(((IDynamicJsonObject)dynamicJsonObject).Inner, indexDefinition,
- Field.Store.NO).ToList(),
+ Fields = anonymousObjectToLuceneDocumentConverter.Index(((IDynamicJsonObject)dynamicJsonObject).Inner, Field.Store.NO).ToList(),
NewDocId = newDocId is DynamicNullObject ? null : (string)newDocId,
ShouldSkip = false
};
@@ -174,7 +173,7 @@ private IndexingResult ExtractIndexDataFromDocument(AnonymousObjectToLuceneDocum
{
properties = TypeDescriptor.GetProperties(doc);
}
- var abstractFields = anonymousObjectToLuceneDocumentConverter.Index(doc, properties, indexDefinition, Field.Store.NO).ToList();
+ var abstractFields = anonymousObjectToLuceneDocumentConverter.Index(doc, properties, Field.Store.NO).ToList();
return new IndexingResult()
{
Fields = abstractFields,
View
11 Raven.Database/Linq/AbstractViewGenerator.cs
@@ -8,9 +8,11 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Text.RegularExpressions;
+using Lucene.Net.Documents;
using Raven.Abstractions.Indexing;
using Raven.Abstractions.Linq;
using System.Linq;
+using Raven.Database.Indexing;
namespace Raven.Database.Linq
{
@@ -87,6 +89,15 @@ protected AbstractViewGenerator()
Indexes = new Dictionary<string, FieldIndexing>();
}
+ protected IEnumerable<AbstractField> CreateField(string name, object value, bool stored = false, bool indexed = true)
+ {
+ var indexDefinition = new IndexDefinition();
+ indexDefinition.Indexes[name] = indexed ? FieldIndexing.Analyzed : FieldIndexing.NotAnalyzed;
+ var anonymousObjectToLuceneDocumentConverter = new AnonymousObjectToLuceneDocumentConverter(indexDefinition);
+
+ return anonymousObjectToLuceneDocumentConverter.CreateFields(name, value, stored ? Field.Store.YES : Field.Store.NO);
+ }
+
protected IEnumerable<dynamic> Hierarchy(object source, string name)
{
var djo = (DynamicJsonObject)source;
View
62 Raven.Tests/Indexes/DynamicFieldIndexing.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Linq;
+using Raven.Client.Indexes;
+using Xunit;
+
+namespace Raven.Tests.Indexes
+{
+ public class DynamicFieldIndexing : RavenTest
+ {
+ public class Item
+ {
+ public string Id { get; set; }
+ public Dictionary<string, string> Values { get; set; }
+ }
+
+ public class WithDynamicIndex : AbstractIndexCreationTask<Item>
+ {
+ public WithDynamicIndex()
+ {
+ Map = items =>
+ from item in items
+ select new
+ {
+ _ = item.Values.Select(x => CreateField(x.Key, x.Value))
+ };
+ }
+ }
+
+ [Fact]
+ public void CanSearchDynamically()
+ {
+ using (var store = NewDocumentStore())
+ {
+ new WithDynamicIndex().Execute(store);
+
+ using (var s = store.OpenSession())
+ {
+ s.Store(new Item
+ {
+ Values = new Dictionary<string, string>
+ {
+ {"Name", "Fitzchak"},
+ {"User", "Admin"}
+ }
+ });
+
+ s.SaveChanges();
+ }
+
+ using (var s = store.OpenSession())
+ {
+ var items = s.Advanced.LuceneQuery<Item, WithDynamicIndex>()
+ .WaitForNonStaleResults()
+ .WhereEquals("Name", "Fitzchak")
+ .ToList();
+
+ Assert.NotEmpty(items);
+ }
+ }
+ }
+ }
+}
View
1 Raven.Tests/Raven.Tests.csproj
@@ -547,6 +547,7 @@
<Compile Include="Indexes\ComplexIndexOnNotAnalyzedField.cs" />
<Compile Include="Indexes\CreateIndexesWithCasting.cs" />
<Compile Include="Indexes\CustomAnalyzer.cs" />
+ <Compile Include="Indexes\DynamicFieldIndexing.cs" />
<Compile Include="Indexes\DynamicQueryMapping.cs" />
<Compile Include="Indexes\ExpressionOperatorPrecedenceTest.cs" />
<Compile Include="Indexes\LinqIndexesFromClient.cs" />

0 comments on commit 2d86d1e

Please sign in to comment.
Something went wrong with that request. Please try again.