Skip to content

Commit

Permalink
Initial spike for true custom sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
ayende committed Dec 22, 2014
1 parent cbbd20d commit 3e7b11b
Show file tree
Hide file tree
Showing 15 changed files with 250 additions and 2 deletions.
1 change: 1 addition & 0 deletions Raven.Abstractions/Data/Constants.cs
Expand Up @@ -27,6 +27,7 @@ static Constants()
public const string SystemDatabase = "<system>";
public const string TemporaryScoreValue = "Temp-Index-Score";
public const string RandomFieldName = "__random";
public const string CustomSortFieldName = "__customSort";
public const string NullValueNotAnalyzed = "[[NULL_VALUE]]";
public const string EmptyStringNotAnalyzed = "[[EMPTY_STRING]]";
public const string NullValue = "NULL_VALUE";
Expand Down
11 changes: 11 additions & 0 deletions Raven.Client.Lightweight/Document/AbstractDocumentQuery.cs
Expand Up @@ -801,6 +801,11 @@ public void RandomOrdering()
AddOrder(Constants.RandomFieldName + ";" + Guid.NewGuid(), false);
}

public void CustomSortUsing(string typeName)
{
AddOrder(Constants.CustomSortFieldName + ";" + typeName, false);
}

/// <summary>
/// Order the search results randomly using the specified seed
/// this is useful if you want to have repeatable random queries
Expand Down Expand Up @@ -2205,6 +2210,12 @@ IDocumentQueryCustomization IDocumentQueryCustomization.RandomOrdering()
return this;
}

IDocumentQueryCustomization IDocumentQueryCustomization.CustomSortUsing(string typeName)
{
CustomSortUsing(typeName);
return this;
}

/// <summary>
/// Order the search results randomly using the specified seed
/// this is useful if you want to have repeatable random queries
Expand Down
5 changes: 5 additions & 0 deletions Raven.Client.Lightweight/Document/IAbstractDocumentQuery.cs
Expand Up @@ -39,6 +39,11 @@ public interface IAbstractDocumentQuery<T>
/// </summary>
void RandomOrdering();

/// <summary>
/// Sort using custom sorter on the server
/// </summary>
void CustomSortUsing(string typeName);

/// <summary>
/// Order the search results randomly using the specified seed
/// this is useful if you want to have repeatable random queries
Expand Down
6 changes: 6 additions & 0 deletions Raven.Client.Lightweight/IDocumentQueryCustomization.cs
Expand Up @@ -151,6 +151,12 @@ public interface IDocumentQueryCustomization
/// </summary>
IDocumentQueryCustomization RandomOrdering();


/// <summary>
/// Sort using custom sorter on the server
/// </summary>
IDocumentQueryCustomization CustomSortUsing(string typeName);

/// <summary>
/// Order the search results randomly using the specified seed
/// this is useful if you want to have repeatable random queries
Expand Down
2 changes: 2 additions & 0 deletions Raven.Database/Data/DynamicQueryMapping.cs
Expand Up @@ -170,6 +170,8 @@ public static DynamicQueryMapping Create(DocumentDatabase database, IndexQuery q
var field = sortedField.Field;
if (field.StartsWith(Constants.RandomFieldName))
continue;
if (field.StartsWith(Constants.CustomSortFieldName))
continue;
if (field == Constants.TemporaryScoreValue)
continue;

Expand Down
8 changes: 8 additions & 0 deletions Raven.Database/Extensions/IndexingExtensions.cs
Expand Up @@ -15,6 +15,7 @@
using Raven.Abstractions.Data;
using Raven.Abstractions.Indexing;
using Raven.Database.Indexing.Sorting;
using Raven.Database.Indexing.Sorting.Custom;
using Raven.Database.Linq;
using Constants = Raven.Abstractions.Data.Constants;

Expand Down Expand Up @@ -176,6 +177,13 @@ public static Sort GetSort(this IndexQuery self, IndexDefinition indexDefinition
return new RandomSortField(Guid.NewGuid().ToString());
return new RandomSortField(parts[1]);
}
if (sortedField.Field.StartsWith(Constants.CustomSortFieldName))
{
var parts = sortedField.Field.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2) // truly random
throw new InvalidOperationException("Cannot figure out type for custom sort");
return new CustomSortField(parts[1], self);
}
if (spatialQuery != null && sortedField.Field == Constants.DistanceFieldName)
{
var spatialField = viewGenerator.GetSpatialField(spatialQuery.SpatialFieldName);
Expand Down
2 changes: 1 addition & 1 deletion Raven.Database/Indexing/Index.cs
Expand Up @@ -1057,7 +1057,7 @@ public static void AssertQueryDoesNotContainFieldsThatAreNotIndexed(IndexQuery i
{
f = f.Substring(0, f.Length - "_Range".Length);
}
if (f.StartsWith(Constants.RandomFieldName))
if (f.StartsWith(Constants.RandomFieldName) || f.StartsWith(Constants.CustomSortFieldName))
continue;
if (viewGenerator.ContainsField(f) == false && f != Constants.DistanceFieldName
&& viewGenerator.ContainsField("_") == false) // the catch all field name means that we have dynamic fields names
Expand Down
44 changes: 44 additions & 0 deletions Raven.Database/Indexing/Sorting/Custom/CustomSortField.cs
@@ -0,0 +1,44 @@
using System;
using Lucene.Net.Search;
using Raven.Abstractions.Data;

namespace Raven.Database.Indexing.Sorting.Custom
{
public class CustomSortField : SortField
{
private readonly IndexEntriesToComparablesGenerator generator;
public CustomSortField(string typeName, IndexQuery indexQuery)
: base(String.Empty, INT)
{
var clrType = System.Type.GetType(typeName, throwOnError: true);
generator = (IndexEntriesToComparablesGenerator)Activator.CreateInstance(clrType, new object[] {indexQuery});
}

public override FieldComparator GetComparator(int numHits, int sortPos)
{
return new CustomSortFieldCompartor(generator, numHits);
}

public override FieldComparatorSource ComparatorSource
{
get
{
return new CustomSortFieldComparatorSource(this);
}
}
private class CustomSortFieldComparatorSource :FieldComparatorSource
{
private readonly CustomSortField parent;

public CustomSortFieldComparatorSource(CustomSortField parent)
{
this.parent = parent;
}

public override FieldComparator NewComparator(string fieldname, int numHits, int sortPos, bool reversed)
{
return new CustomSortFieldCompartor(parent.generator, numHits);
}
}
}
}
58 changes: 58 additions & 0 deletions Raven.Database/Indexing/Sorting/Custom/CustomSortFieldCompartor.cs
@@ -0,0 +1,58 @@
// -----------------------------------------------------------------------
// <copyright file="CustomSortFieldCompartor.cs" company="Hibernating Rhinos LTD">
// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
using Lucene.Net.Index;
using Lucene.Net.Search;

namespace Raven.Database.Indexing.Sorting.Custom
{
public class CustomSortFieldCompartor : FieldComparator
{
private readonly IndexEntriesToComparablesGenerator generator;
private readonly IComparable[] values;
private IComparable bottom;
private IndexReader currentReader;

public CustomSortFieldCompartor(IndexEntriesToComparablesGenerator generator, int numHits)
{
this.generator = generator;
values= new IComparable[numHits];
}

public override int Compare(int slot1, int slot2)
{
var a = values[slot1];
var b = values[slot2];
return a.CompareTo(b);
}

public override void SetBottom(int slot)
{
bottom = values[slot];
}

public override int CompareBottom(int doc)
{
var current = generator.Generate(currentReader,doc);
return bottom.CompareTo(current);
}

public override void Copy(int slot, int doc)
{
values[slot] = generator.Generate(currentReader, doc);
}

public override void SetNextReader(IndexReader reader, int docBase)
{
currentReader = reader;
}

public override IComparable this[int slot]
{
get { return values[slot]; }
}
}
}
@@ -0,0 +1,18 @@
using System;
using Lucene.Net.Index;
using Raven.Abstractions.Data;

namespace Raven.Database.Indexing.Sorting.Custom
{
public abstract class IndexEntriesToComparablesGenerator
{
protected IndexQuery IndexQuery;

protected IndexEntriesToComparablesGenerator(IndexQuery indexQuery)
{
IndexQuery = indexQuery;
}

public abstract IComparable Generate(IndexReader reader, int doc);
}
}
4 changes: 3 additions & 1 deletion Raven.Database/Queries/DynamicQueryOptimizer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Lucene.Net.Index;
using Raven.Abstractions.Data;
using Raven.Abstractions.Indexing;
using System.Linq;
Expand Down Expand Up @@ -223,7 +224,8 @@ public class Explanation
var normalizedFieldName = DynamicQueryMapping.ReplaceInvalidCharactersForFields(sortedField.Field);
if (normalizedFieldName.EndsWith("_Range"))
normalizedFieldName = normalizedFieldName.Substring(0, normalizedFieldName.Length - "_Range".Length);
if (normalizedFieldName.StartsWith(Constants.RandomFieldName))
if (normalizedFieldName.StartsWith(Constants.RandomFieldName) ||
normalizedFieldName.StartsWith(Constants.CustomSortFieldName))
continue; // virtual field that we don't sort by
// if the field is not in the output, then we can't sort on it.
Expand Down
3 changes: 3 additions & 0 deletions Raven.Database/Raven.Database.csproj
Expand Up @@ -225,6 +225,9 @@
<Compile Include="Actions\QueryActions.cs" />
<Compile Include="Actions\SubscriptionActions.cs" />
<Compile Include="Actions\TaskActions.cs" />
<Compile Include="Indexing\Sorting\Custom\CustomSortField.cs" />
<Compile Include="Indexing\Sorting\Custom\CustomSortFieldCompartor.cs" />
<Compile Include="Indexing\Sorting\Custom\IndexEntriesToComparablesGenerator.cs" />
<Compile Include="Server\WebApi\RavenInlineConstraintResolver.cs" />
<Compile Include="Server\WebApi\Attributes\RavenRouteAttribute.cs" />
<Compile Include="DiskIO\DiskPerformanceTester.cs" />
Expand Down
2 changes: 2 additions & 0 deletions Raven.Tests/Raven.Tests.csproj
Expand Up @@ -627,6 +627,8 @@
<Compile Include="Smuggler\SmugglerExecutionTests.cs" />
<Compile Include="Smuggler\SmugglerIdentitiesTests.cs" />
<Compile Include="Smuggler\SmugglerTransformScriptsTests.cs" />
<Compile Include="Sorting\CustomSorting.cs" />
<Compile Include="Sorting\SortByNumberOfCharactersFromEnd.cs" />
<Compile Include="Spatial\BoundingBoxIndexTests.cs" />
<Compile Include="Spatial\CartesianTests.cs" />
<Compile Include="Spatial\Clustering.cs" />
Expand Down
59 changes: 59 additions & 0 deletions Raven.Tests/Sorting/CustomSorting.cs
@@ -0,0 +1,59 @@
// -----------------------------------------------------------------------
// <copyright file="CustomSorting.cs" company="Hibernating Rhinos LTD">
// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System.Linq;
using Jint.Parser;
using Raven.Abstractions.Indexing;
using Raven.Client.Indexes;
using Raven.Tests.Common;
using Xunit;

namespace Raven.Tests.Sorting
{
public class CustomSorting : RavenTest
{
public class User
{
public string Name;
}

public class User_Search : AbstractIndexCreationTask<User>
{
public User_Search()
{
Map = users =>
from user in users
select new { user.Name };

Store(x => x.Name, FieldStorage.Yes);
}
}

[Fact]
public void Normal()
{
using (var store = NewDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new User { Name = "Maxim" });
session.Store(new User { Name = "Oren" });
session.SaveChanges();
}

new User_Search().Execute(store);
WaitForIndexing(store);

using (var session = store.OpenSession())
{
var users = session.Query<User, User_Search>()
.Customize(x => x.CustomSortUsing(typeof (SortByNumberOfCharactersFromEnd).AssemblyQualifiedName))
.AddTransformerParameter("len", 1)
.ToList();
}
}
}
}
}
29 changes: 29 additions & 0 deletions Raven.Tests/Sorting/SortByNumberOfCharactersFromEnd.cs
@@ -0,0 +1,29 @@
// -----------------------------------------------------------------------
// <copyright file="SortByNumberOfCharactersFromEnd.cs" company="Hibernating Rhinos LTD">
// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
using Lucene.Net.Index;
using Raven.Abstractions.Data;
using Raven.Database.Indexing.Sorting.Custom;

namespace Raven.Tests.Sorting
{
public class SortByNumberOfCharactersFromEnd : IndexEntriesToComparablesGenerator
{
private readonly int len;

public SortByNumberOfCharactersFromEnd(IndexQuery indexQuery) : base(indexQuery)
{
len = IndexQuery.TransformerParameters["len"].Value<int>();
}

public override IComparable Generate(IndexReader reader, int doc)
{
var document = reader.Document(doc);
var name = document.GetField("Name").StringValue;
return name.Substring(name.Length - len, len);
}
}
}

0 comments on commit 3e7b11b

Please sign in to comment.