foursquare/rogue — pull request #34 by nsanch (2011-11-05 00:00:25+00:00)
Title: Compile time indexes

This is a follow-up to the accidental pull request I sent last night.

Let's suppose there were a {mayor: 1, _id: 1, closed: 1} on Venue declared as Venue.mayorIdClosedIdx. To state that you're using it and then query on all the fields, you'd do:

Venue.useIndex(Venue.mayorIdClosedIdx)
.where(_.mayor)(_ eqs 2097)
.where(_._id)(_ eqs new ObjectId())
.where(_.closed)(_ eqs false)

To only query on mayor and closed, and skip past _id, you'd need to add a rangeScan clause to indicate that you're skipping past that index:

Venue.useIndex(Venue.mayorIdClosedIdx)
.where(_.mayor)(_ eqs 2097)
.rangeScan(_._id)
.where(_.closed)(_ eqs false)

It's possible to pass multiple fields to rangeScan, so if you want to skip both _id and closed and then query on something else, you can do that too:

Venue.useIndex(Venue.mayorIdClosedIdx)
.where(_.mayor)(_ eqs 2097)
.rangeScan(_._id, _.closed)
.scan(_.legacyid eqs 128530)

The enforcement is pretty simple, and definitely does not cover all cases. Quick summary:

1. You must use the first column of an index.
2. If you pass a clause to .where() it must have Index behavior according to the Query, not IndexScan or DocumentScan.
3. If one field on the index is an iscan, subsequent fields must also be marked as iscan.

I did a bit of refactoring to do this, including these two changes:

* Changed IndexBehavior from an enum to a set of traits and case objects that inherit from the MaybeIndexed, Indexable, and IndexScannable marker traits.
* Made a bunch of new QueryClause's that specify their IndexBehavior directly as a type parameter to a new IndexableQueryClause base class.