Sunday, October 31, 2010

RavenDb Indexes : 1st Attempt

I’ve been playing with RavenDb’s pre-defined indexes today. They use a map reduce model with Linq, which is very nice. A recent new feature is the ability to define them in your application code. Here a couple I’ve just created:

public class Child_ByName : AbstractIndexCreationTask<Child>
{
    public Child_ByName()
    {
        Map = children => from child in children
                          select new { child.Name };
    }
}

public class Child_ByPendingSchedule : AbstractIndexCreationTask<Child>
{
    public Child_ByPendingSchedule()
    {
        Map = children => from child in children
                          from schedule in child.Account.PaymentSchedules
                          select new { schedule.NextRun };
    }
}

You register them with the RavenDb document store (a bit like a session factory in NHibernate) like this:

IndexCreation.CreateIndexes(typeof(Child_ByName).Assembly, store);

When you want to use them, you have to use a rather nasty magic string (RavenDb converts the underscore in the class name to a forward slash ‘/’).

var results = session.Advanced.LuceneQuery<Child>("Child/ByName").WhereEquals("Name", "two").ToList();

It would be much nicer if you could write something like this instead:

var results = session.Advanced.LuceneQuery<Child_ByName>().WhereEquals("Name", "two").ToList();

Here a couple of tests showing them at work:

[TestFixture]
public class RavenDbSchedulingWithIndex : LocalClientTest
{
    IDocumentStore store;
    Parent parent;
    DateTime now;

    [SetUp]
    public void SetUp()
    {
        store = NewDocumentStore();
        IndexCreation.CreateIndexes(typeof(Child_ByName).Assembly, store);
        parent = new Parent("Dad", "mike@mike.com", "xxx");

        now = new DateTime(2010, 4, 5);

        // create some children with accounts and schedules
        using (var session = store.OpenSession())
        {
            session.Store(CreateChildWithSchedule("one", 1M, now.AddDays(-2)));
            session.Store(CreateChildWithSchedule("two", 2M, now.AddDays(-1)));
            session.Store(CreateChildWithSchedule("three", 3M, now));
            session.Store(CreateChildWithSchedule("four", 4M, now.AddDays(1)));
            session.Store(CreateChildWithSchedule("five", 5M, now.AddDays(2)));
            session.SaveChanges();
        }
    }

    [Test]
    public void Select_child_using_ByName_index()
    {
        using (var session = store.OpenSession())
        {
            var results = session.Advanced.LuceneQuery<Child>("Child/ByName").WhereEquals("Name", "two").WaitForNonStaleResults().ToList();
            results.Count().ShouldEqual(1);
            results[0].Name.ShouldEqual("two");
        }
    }

    [Test]
    public void Select_child_using_ByPendingSchedule()
    {
        using (var session = store.OpenSession())
        {
            var results = session.Advanced
                .LuceneQuery<Child>("Child/ByPendingSchedule")
                .WhereLessThan("NextRun", now)
                .WaitForNonStaleResults().ToList();

            results.Count().ShouldEqual(2);
            results[0].Name.ShouldEqual("one");
            results[1].Name.ShouldEqual("two");
        }
    }

    Child CreateChildWithSchedule(string name, decimal amount, DateTime startDate)
    {
        var child = parent.CreateChild(name, name, "xxx");
        child.Account.AddPaymentSchedule(startDate, Interval.Week, amount, "Pocket Money");
        return child;
    }
    
}

This is some spike code from Tardis Bank. You can checkout the complete project here:

http://github.com/mikehadlow/Suteki.TardisBank

3 comments:

Rob Ashton said...

Um, you can do Query

And LuceneQuery, at least I know you can in the unstable branch, I don't know if that's been moved to stable yet.

Rob Ashton said...

Okay, so Blogger strips out angle brackets, I was trying to say you can specify the index


http://codeofrob.com/archive/2010/10/24/ravendbndashthe-image-gallery-project-xv-improving-tag-search-with-autocomplete.aspx

Example is there, works in a similar fashion with the Lucene stuff too

Mike Hadlow said...

Hi Rob,

Yes, I've been using Query as a first choice. For what I wanted to do here, there are some missing corners of the Linq implementation, so it's nice to be able to fall back on indexes. Here's the discussion on the mailing list:

http://groups.google.com/group/ravendb/browse_thread/thread/c5400e00d289a5f8