Multi-Map Indexes
-
Multi-Map indexes allow you to index data from multiple collections, like polymorphic data or any data common to different types.
-
Learn how to index polymorphic data
Learn how to create Multi-Map-Reduce indexes -
In this page:
addMap
The addMap
method is used to map fields from a single collection, e.g. Dogs
.
Let's assume that we have Dog
and Cat
classes, both inheriting from the class Animal
:
_1
/** @var array<Smart_Search_Projection> $results */
$results = $session
->documentQuery(Smart_Search_Result::class, Smart_Search::class)
->search("Content", "Lau*")
->selectFields(Smart_Search_Projection::class)
->toList();
/** @var Smart_Search_Projection $result */
foreach ($results as $result)
{
echo $result->getCollection() . ": " . $result->getDisplayName();
// Companies: Laughing Bacchus Wine Cellars
// Products: Laughing Lumberjack Lager
// Employees: Laura Callahan
}
class Cat extends Animal
{
}
abstract class Animal implements AnimalInterface
{
public ?string $name = null;
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
}
We can define our index using addMap
and query it as follows:
class Animals_ByName extends AbstractMultiMapIndexCreationTask
{
public function __construct()
{
parent::__construct();
$this->addMap( "docs.Cats.Select(c => new { " .
" Name = c.Name " .
"})");
$this->addMap( "docs.Dogs.Select(d => new { " .
" Name = 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}})"
]);
}
}
/** @var array<AnimalInterface> $results */
$results = $session
->query(AnimalInterface::class, Animals_ByName::class)
->whereEquals("Name", "Mitzy")
->toList();
from index 'Animals/ByName'
where Name = 'Mitzy'
Searching across multiple collections
Another great usage of Multi-Map indexes is smart-search.
To search for products, companies, or employees by their name, you need to define the following index:
class Smart_Search_Result
{
private ?string $id = null;
private ?string $displayName = null;
private ?string $collection = null;
private ?string $content = null;
public function getId(): ?string
{
return $this->id;
}
public function setId(?string $id): void
{
$this->id = $id;
}
public function getDisplayName(): ?string
{
return $this->displayName;
}
public function setDisplayName(?string $displayName): void
{
$this->displayName = $displayName;
}
public function getCollection(): ?string
{
return $this->collection;
}
public function setCollection(?string $collection): void
{
$this->collection = $collection;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): void
{
$this->content = $content;
}
}
class Smart_Search_Projection
{
private ?string $id = null;
private ?string $displayName = null;
private ?string $collection = null;
public function getId(): ?string
{
return $this->id;
}
public function setId(?string $id): void
{
$this->id = $id;
}
public function getDisplayName(): ?string
{
return $this->displayName;
}
public function setDisplayName(?string $displayName): void
{
$this->displayName = $displayName;
}
public function getCollection(): ?string
{
return $this->collection;
}
public function setCollection(?string $collection): void
{
$this->collection = $collection;
}
}
class Smart_Search extends AbstractMultiMapIndexCreationTask
{
public function __construct()
{
parent::__construct();
$this->addMap("docs.Companies.Select(c => new { " .
" Id = Id(c), " .
" Content = new string[] { " .
" c.Name " .
" }, " .
" DisplayName = c.Name, " .
" Collection = this.MetadataFor(c)[\"@collection\"] " .
"})");
$this->addMap("docs.Products.Select(p => new { " .
" Id = Id(p), " .
" Content = new string[] { " .
" p.Name " .
" }, " .
" DisplayName = p.Name, " .
" Collection = this.MetadataFor(p)[\"@collection\"] " .
"})");
$this->addMap("docs.Employees.Select(e => new { " .
" Id = Id(e), " .
" Content = new string[] { " .
" e.FirstName, " .
" e.LastName " .
" }, " .
" DisplayName = (e.FirstName + \" \") + e.LastName, " .
" Collection = this.MetadataFor(e)[\"@collection\"] " .
"})");
// mark 'content' field as analyzed which enables full text search operations
$this->index("Content", FieldIndexing::search());
// storing fields so when projection (e.g. ProjectInto)
// requests only those fields
// then data will come from index only, not from storage
$this->store("Id", FieldStorage::yes());
$this->store("DisplayName", FieldStorage::yes());
$this->store("Collection", FieldStorage::yes());
}
}
and query it using:
/** @var array<Smart_Search_Projection> $results */
$results = $session
->documentQuery(Smart_Search_Result::class, Smart_Search::class)
->search("Content", "Lau*")
->selectFields(Smart_Search_Projection::class)
->toList();
/** @var Smart_Search_Projection $result */
foreach ($results as $result)
{
echo $result->getCollection() . ": " . $result->getDisplayName();
// Companies: Laughing Bacchus Wine Cellars
// Products: Laughing Lumberjack Lager
// Employees: Laura Callahan
}
Remarks
Remember that all map functions must output objects
with an identical shape (the field names have to match).