Bundle: Scripted Index Results

Scripted Index Results bundle allows you to attach scripts to indexes. Those scripts can operate on the results of the indexing. This creates new opportunities, such as modification of documents by index calculated values or recursive map/reduce indexes.

In order to enable this bundle, you need to add the ScriptedIndexResults to the Raven/ActiveBundles while setting off a database document when a database is created (or via the Studio):

store
	.DatabaseCommands
	.GlobalAdmin
	.CreateDatabase(
		new DatabaseDocument
			{
				Id = "Northwind",
				Settings =
					{
						{ "Raven/ActiveBundles", "ScriptedIndexResults" }
					}
			});

The activation of the bundle adds a database index update trigger which is run when an index entry is created or deleted. In order to take advantage of this feature for a selected index, you need to put a special set up document under the key Raven/ScriptedIndexResults/[IndexName] that will contain the appropriate scripts to apply:

using (var session = store.OpenSession())
{
	session.Store(new Abstractions.Data.ScriptedIndexResults
					  {
						  Id = Abstractions.Data.ScriptedIndexResults.IdPrefix + "IndexName",
						  IndexScript = @"", // index script
						  DeleteScript = @"" // delete script body
					  });

	session.SaveChanges();
}

Example I - basics

Let us assume that we have the following index:

public class Orders_ByCompany : AbstractIndexCreationTask<Order, Orders_ByCompany.Result>
{
	public class Result
	{
		public string Company { get; set; }

		public int Count { get; set; }

		public decimal Total { get; set; }
	}

	public Orders_ByCompany()
	{
		Map = orders => from order in orders
						select new
						{
							order.Company,
							Count = 1,
							Total = order.Lines.Sum(l => (l.Quantity * l.PricePerUnit) * (1 - l.Discount))
						};

		Reduce = results => from result in results
							group result by result.Company into g
							select new
							{
								Company = g.Key,
								Count = g.Sum(x => x.Count),
								Total = g.Sum(x => x.Total)
							};
	}
}

Now we want to embed the reduced values inside the company document, so let's create the setup document:

using (var session = store.OpenSession())
{
	session.Store(new Abstractions.Data.ScriptedIndexResults
					  {
						  Id = Abstractions.Data.ScriptedIndexResults.IdPrefix + new Orders_ByCompany().IndexName,
						  IndexScript = @"
			var company = LoadDocument(this.Company);
			if(company == null)
					return;
			company.Orders = { Count: this.Count, Total: this.Total };
			PutDocument(this.Company, company);
		",
						  DeleteScript = @"
			var company = LoadDocument(key);
			if(company == null)
					return;
			delete company.Orders;
			PutDocument(key, company);
		"
					  });
}

Since this document is stored in the database every time the Orders/ByCompany index creates a new index entry, the IndexScript will be applied to reduce results. Under this keyword in the IndexScript and DeleteScript script you have an access to the Lucene document stored in index ( Company , Count , and Total values). As you can see, the script uses the built-in LoadDocument and PutDocument functions in order to modify a company document. Note that we need to ensure that if the index entry is deleted, we will revert the changes by using the DeleteScript script. Notice that we no longer have access to our calculated values under this, and the only available variable is a 'key', which is a document key.

Now, if you take a look at the documents from the companies collection after orders indexation, you will see the added values. For example:

{ 
	"Id" : "companies/1", 
	...
	"Orders" : {
		"Count" : 7,
		"Total" : 1234
	}
}

Example II - AbstractScriptedIndexCreationTask

For easier configuration we have created AbstractScriptedIndexCreationTask where you can specify both, index definition and scripted index setup document. Each time the task is executed, it will update (if needed) stored index definition, stored setup document and reset index if any of those changed.

public class Orders_ByCompany : AbstractScriptedIndexCreationTask<Order, Orders_ByCompany.Result>
{
	public class Result
	{
		public string Company { get; set; }

		public int Count { get; set; }

		public decimal Total { get; set; }
	}

	public Orders_ByCompany()
	{
		Map = orders => from order in orders
						select new
						{
							order.Company,
							Count = 1,
							Total = order.Lines.Sum(l => (l.Quantity * l.PricePerUnit) * (1 - l.Discount))
						};

		Reduce = results => from result in results
							group result by result.Company into g
							select new
							{
								Company = g.Key,
								Count = g.Sum(x => x.Count),
								Total = g.Sum(x => x.Total)
							};

		IndexScript = @"
					var company = LoadDocument(this.Company);
					if(company == null)
							return;
					company.Orders = { Count: this.Count, Total: this.Total };
					PutDocument(this.Company, company);
				";

		DeleteScript = @"
					var company = LoadDocument(key);
					if(company == null)
							return;
					delete company.Orders;
					PutDocument(key, company);
				";
	}
}