Indexing Polymorphic Data



Polymorphic Data

Let's assume, for example, that we have the following inheritance hierarchy:

Figure 1: Polymorphic indexes

When saving a Cat document, it will be assigned to the "Cats" collection,
while a Dog document will be placed in the "Dogs" collection.

If we intend to create a simple Map-index for Cat documents based on their names, we would write:

from cat in docs.Cats
select new { cat.Name }

And for dogs:

from dog in docs.Dogs
select new { dog.Name }

The challenge

Querying each index results in documents only from the specific collection the index was defined for.
However, what if we need to query across ALL animal collections?

Multi-Map Indexes

The easiest way to do this is by writing a multi-map index such as:

class Animals_ByName extends AbstractMultiMapIndexCreationTask
{
    public function __construct()
    {
        parent::__construct();

        $this->addMap("from c in docs.Cats select new { c.name }");
        $this->addMap("from d in docs.Dogs select new { d.name }");
    }
}
class Animals_ByName extends AbstractJavaScriptIndexCreationTask
{
    public function __construct()
    {
        parent::__construct();

        $this->setMaps([
            "map('cats', function (c){ return {Name: c.Name}})",
            "map('dogs', function (d){ return {Name: d.Name}})"
        ]);
    }
}

And query it like this:

/** @var array<Animal> $results */
$results = $session
    ->advanced()
    ->documentQuery(Animal::class, Animals_ByName::class)
    ->whereEquals("Name", "Mitzy")
    ->toList();
from index 'Animals/ByName'
where Name = 'Mitzy'

Other Options

Another option would be to modify the way we generate the Collection for subclasses of Animal:

$store = new DocumentStore();
$store->getConventions()->setFindCollectionName(
    function (?string $className): string {
        if (is_a($className, Animal::class)) {
            return "Animals";
        }
        return DocumentConventions::defaultGetCollectionName($className);
    }
);

Using this method, we can now index on all animals using:

from animal in docs.Animals
select new { animal.Name }

But what happens when you don't want to modify the entity name of an entity itself?

You can create a polymorphic index using:

from animal in docs.WhereEntityIs("Cats", "Dogs")
select new { animal.Name }

It will generate an index that matches both Cats and Dogs.