Querying: Intersection


  • To allow users to intersect queries on the server-side and return only documents that match all the provided sub-queries, we introduced the query intersection feature.

  • In this page:


Intersection

Let's consider a case where we have a T-Shirt class:

class TShirt
{
    private ?string $id = null;
    private ?int $releaseYear = null;
    private ?string $manufacturer = null;
    private ?TShirtTypeList $types = null;

    public function getId(): ?string
    {
        return $this->id;
    }

    public function setId(?string $id): void
    {
        $this->id = $id;
    }

    public function getReleaseYear(): ?int
    {
        return $this->releaseYear;
    }

    public function setReleaseYear(?int $releaseYear): void
    {
        $this->releaseYear = $releaseYear;
    }

    public function getManufacturer(): ?string
    {
        return $this->manufacturer;
    }

    public function setManufacturer(?string $manufacturer): void
    {
        $this->manufacturer = $manufacturer;
    }

    public function getTypes(): ?TShirtTypeList
    {
        return $this->types;
    }

    public function setTypes(?TShirtTypeList $types): void
    {
        $this->types = $types;
    }
}

class TShirtType
{
    private ?string $color = null;
    private ?string $size = null;

    public function __construct(?string $color, ?string $size)
    {
        $this->color = $color;
        $this->size = $size;
    }

    public function getColor(): ?string
    {
        return $this->color;
    }

    public function setColor(?string $color): void
    {
        $this->color = $color;
    }

    public function getSize(): ?string
    {
        return $this->size;
    }

    public function setSize(?string $size): void
    {
        $this->size = $size;
    }
}

class TShirtTypeList extends TypedList
{
    protected function __construct()
    {
        parent::__construct(TShirtType::class);
    }
}

We will fill our database with a few records:

// first tShirt
$tShirt = new TShirt();
$tShirt->setId("tshirts/1");
$tShirt->setManufacturer("Raven");
$tShirt->setReleaseYear(2010);

$types = TShirtTypeList::fromArray([
    new TShirtType(color: "Blue", size: "Small"),
    new TShirtType(color: "Black", size: "Small"),
    new TShirtType(color: "Black", size: "Medium"),
    new TShirtType(color: "Gray", size: "Large")
]);
$tShirt->setTypes($types);

$session->store($tShirt);

// second tShirt
$tShirt = new TShirt();
$tShirt->setId("tshirts/2");
$tShirt->setManufacturer("Wolf");
$tShirt->setReleaseYear(2011);

$types = TShirtTypeList::fromArray([
    new TShirtType(color: "Blue", size: "Small"),
    new TShirtType(color: "Black", size: "Large"),
    new TShirtType(color: "Black", size: "Medium")
]);
$tShirt->setTypes($types);

$session->store($tShirt);

// third tShirt
$tShirt = new TShirt();
$tShirt->setId("tshirts/3");
$tShirt->setManufacturer("Raven");
$tShirt->setReleaseYear(2011);

$types = TShirtTypeList::fromArray([
    new TShirtType(color: "Yellow", size: "Small"),
    new TShirtType(color: "Gray", size: "Large")
]);
$tShirt->setTypes($types);

$session->store($tShirt);

// fourth tShirt
$tShirt = new TShirt();
$tShirt->setId("tshirts/4");
$tShirt->setManufacturer("Raven");
$tShirt->setReleaseYear(2012);

$types = TShirtTypeList::fromArray([
    new TShirtType(color: "Blue", size: "Small"),
    new TShirtType(color: "Gray", size: "Large")
]);
$tShirt->setTypes($types);

$session->store($tShirt);

Now we can use the intersect method to return all the T-shirts that are manufactured by Raven and contain both Small Blue and Large Gray types.

/** @var array<TShirt> $results */
$results = $session->query(TShirts_ByManufacturerColorSizeAndReleaseYear_Result::class, TShirts_ByManufacturerColorSizeAndReleaseYear::class)
    ->whereEquals("Manufacturer", "Raven")
    ->intersect()
    ->whereEquals("Color", "Blue")
    ->andAlso()
    ->whereEquals("Size", "Small")
    ->intersect()
    ->whereEquals("Color", "Gray")
    ->andAlso()
    ->whereEquals("Size", "Large")
    ->ofType(TShirt::class)
    ->toList();
class TShirts_ByManufacturerColorSizeAndReleaseYear_Result
{
    private ?string $manufacturer = null;
    private ?string $color = null;
    private ?string $size = null;
    private ?int $releaseYear = null;
    // ... getters and setters
}
class TShirts_ByManufacturerColorSizeAndReleaseYear extends AbstractIndexCreationTask
{
    public function __construct()
    {
        parent::__construct();

        $this->map = "docs.TShirts.SelectMany(tshirt => tshirt.types, (tshirt, type) => new {" .
            "    manufacturer = tshirt.manufacturer," .
            "    color = type.color," .
            "    size = type.size," .
            "    releaseYear = tshirt.releaseYear" .
            "})";
    }
}
from index 'TShirts/ByManufacturerColorSizeAndReleaseYear' 
where intersect(Manufacturer = 'Raven', Color = 'Blue' and Size = 'Small', Color = 'Gray' and Size = 'Large')

The above query will return tshirts/1 and tshirts/4, that match all sub-queries.
tshirts/2 will not be included in the results because it is not manufactured by Raven, and tshirts/3 will not be included because it is not available in Small Blue.