Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support search indexes in mappings and SchemaManager #2630

Merged
merged 15 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- dependencies: "lowest"
php-version: "8.1"
mongodb-version: "5.0"
driver-version: "1.11.0"
driver-version: "1.17.0"
topology: "server"
symfony-version: "stable"
# Test with highest dependencies
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
],
"require": {
"php": "^8.1",
"ext-mongodb": "^1.11",
"ext-mongodb": "^1.17",
"doctrine/cache": "^1.11 || ^2.0",
"doctrine/collections": "^1.5 || ^2.0",
"doctrine/event-manager": "^1.0 || ^2.0",
"doctrine/instantiator": "^1.1 || ^2",
"doctrine/persistence": "^3.2",
"friendsofphp/proxy-manager-lts": "^1.0",
"jean85/pretty-package-versions": "^1.3.0 || ^2.0.1",
"mongodb/mongodb": "^1.10.0",
"mongodb/mongodb": "^1.17.0",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0",
Expand Down
54 changes: 54 additions & 0 deletions docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,60 @@ Optional attributes:
*/
private $cart;

@SearchIndex
------------

This annotation is used to specify :ref:`search indexes <search_indexes>` for
`MongoDB Atlas Search <https://www.mongodb.com/docs/atlas/atlas-search/>`__.

The attributes correspond to arguments for
`MongoDB\Collection::createSearchIndex() <https://www.mongodb.com/docs/php-library/current/reference/method/MongoDBCollection-createSearchIndex/>`__.
Excluding ``name``, attributes are used to create the
`search index definition <https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/#search-index-definition-syntax>`__.

Optional attributes:

-
``name`` - Name of the search index to create, which must be unique to the
collection. Defaults to ``"default"``.
-
``dynamic`` - Enables or disables dynamic field mapping for this index.
If ``true``, the index will include all fields with
`supported data types <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/#std-label-bson-data-chart>`__.
If ``false``, the ``fields`` attribute must be specified. Defaults to ``false``.
-
``fields`` - Associative array of `field mappings <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/>`__
that specify the fields to index (keys). Required only if dynamic mapping is disabled.
-
``analyzer`` - Specifies the `analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/>`__
to apply to string fields when indexing. Defaults to the
`standard analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/standard/>`__.
-
``searchAnalyzer`` - Specifies the `analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/>`__
to apply to query text before the text is searched. Defaults to the
``analyzer`` attribute, or the `standard analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/standard/>`__.
if both are unspecified.
-
``analyzers`` - Array of `custom analyzers <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/custom/>`__
to use in this index.
-
``storedSource`` - Specifies document fields to store for queries performed
using the `returnedStoredSource <https://www.mongodb.com/docs/atlas/atlas-search/return-stored-source/>`__
option. Specify ``true`` to store all fields, ``false`` to store no fields,
or a `document <https://www.mongodb.com/docs/atlas/atlas-search/stored-source-definition/#std-label-fts-stored-source-document>`__
to specify individual fields to include or exclude from storage. Defaults to ``false``.
-
``synonyms`` - Array of `synonym mapping definitions <https://www.mongodb.com/docs/atlas/atlas-search/synonyms/>`__
to use in this index.

.. note::

Search indexes have some notable differences from `@Index`_. They may only
be defined on document classes. Definitions will not be incorporated from
embedded documents. Additionally, ODM will **NOT** translate field names in
search index definitions. Database field names must be used instead of
mapped field names (i.e. PHP property names).

@ShardKey
---------

Expand Down
188 changes: 188 additions & 0 deletions docs/en/reference/search-indexes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
.. _search_indexes:

Search Indexes
==============

In addition to standard :ref:`indexes <indexes>`, ODM allows you to define
search indexes for use with `MongoDB Atlas Search <https://www.mongodb.com/docs/atlas/atlas-search/>`__.
Search indexes may be queried using the `$search <https://www.mongodb.com/docs/atlas/atlas-search/aggregation-stages/search/>`__
and `$searchMeta <https://www.mongodb.com/docs/atlas/atlas-search/aggregation-stages/searchMeta/>`__
aggregation pipeline stages.

Search indexes have some notable differences from regular
:ref:`indexes <indexes>` in ODM. They may only be defined on document classes.
Definitions will not be incorporated from embedded documents. Additionally, ODM
will **NOT** translate field names in search index definitions. Database field
names must be used instead of mapped field names (i.e. PHP property names).

Search Index Options
--------------------

Search indexes are defined using a more complex syntax than regular
:ref:`indexes <indexes>`.

ODM supports the following search index options:

-
``name`` - Name of the search index to create, which must be unique to the
collection. Defaults to ``"default"``.
-
``dynamic`` - Enables or disables dynamic field mapping for this index.
If ``true``, the index will include all fields with
`supported data types <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/#std-label-bson-data-chart>`__.
If ``false``, the ``fields`` attribute must be specified. Defaults to ``false``.
-
``fields`` - Associative array of `field mappings <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/>`__
that specify the fields to index (keys). Required only if dynamic mapping is disabled.
-
``analyzer`` - Specifies the `analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/>`__
to apply to string fields when indexing. Defaults to the
`standard analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/standard/>`__.
-
``searchAnalyzer`` - Specifies the `analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/>`__
to apply to query text before the text is searched. Defaults to the
``analyzer`` attribute, or the `standard analyzer <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/standard/>`__.
if both are unspecified.
-
``analyzers`` - Array of `custom analyzers <https://www.mongodb.com/docs/atlas/atlas-search/analyzers/custom/>`__
to use in this index.
-
``storedSource`` - Specifies document fields to store for queries performed
using the `returnedStoredSource <https://www.mongodb.com/docs/atlas/atlas-search/return-stored-source/>`__
option. Specify ``true`` to store all fields, ``false`` to store no fields,
or a `document <https://www.mongodb.com/docs/atlas/atlas-search/stored-source-definition/#std-label-fts-stored-source-document>`__
to specify individual fields to include or exclude from storage. Defaults to ``false``.
-
``synonyms`` - Array of `synonym mapping definitions <https://www.mongodb.com/docs/atlas/atlas-search/synonyms/>`__
to use in this index.

Additional documentation for defining search indexes may be found in
`search index definition <https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/#search-index-definition-syntax>`__
within the MongoDB manual.

Static Mapping
--------------

`Static mapping <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/#static-mappings>`__
can be used to configure indexing of specific fields within a document.

The following example demonstrates how to define a search index using static
mapping.

.. configuration-block::

.. code-block:: php

<?php

/**
* @Document
* @SearchIndex(
* name="usernameAndAddresses",
* fields={
* "username"={
* {"type"="string"},
* {"type"="autocomplete"},
* },
* "addresses"={"type"="embeddedDocuments", "dynamic"=true},
* },
* )
*/
class User
{
/** @Id */
private $id;

/** @Field(type="string") */
private $username;

/** @EmbedMany(targetDocument=Address::class) */
private $addresses;

// ...
}

.. code-block:: xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping.xsd">

<document name="Documents\User">
<search-indexes>
<search-index name="usernameAndAddresses">
<field name="username" type="string" />
<field name="username" type="autocomplete" />
<field name="addresses" type="embeddedDocuments" dynamic="true" />
</search-index>
</search-indexes>

<!-- ... -->
</document>
</doctrine-mongo-mapping>

The ``username`` field will indexed both as a string and for autocompletion.
Since the ``addresses`` field uses an :ref:`embed-many <embed_many>`
relationship, it must be indexed using the ``embeddedDocuments`` type; however,
embedded documents within the array are permitted to use dynamic mapping.

Dynamic Mapping
---------------

`Dynamic mapping <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/#dynamic-mappings>`__
can be used to automatically index fields with
`supported data types <https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/#std-label-bson-data-chart>`__
within a document. Dynamically mapped indexes occupy more disk space than
statically mapped indexes and may be less performant; however, they may be
useful if your schema changes or for when experimenting with Atlas Search

.. note::

Atlas Search does **NOT** dynamically index embedded documents contained
within arrays (e.g. :ref:`embed-many <embed_many>` relationships). You must
use static mappings with the `embeddedDocument <https://www.mongodb.com/docs/atlas/atlas-search/field-types/embedded-documents-type/>`__
field type.

The following example demonstrates how to define a search index using dynamic
mapping:

.. configuration-block::

.. code-block:: php

<?php

/**
* @Document
* @SearchIndex(dynamic=true)
*/
class BlogPost
{
/** @Id */
private $id;

/** @Field(type="string") */
private $title;

/** @Field(type="string") */
private $body;

// ...
}

.. code-block:: xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/orm/doctrine-mongo-mapping.xsd">

<document name="Documents\BlogPost">
<search-indexes>
<search-index dynamic="true" />
</search-indexes>

<!-- ... -->
</document>
</doctrine-mongo-mapping>
1 change: 1 addition & 0 deletions docs/en/sidebar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
reference/bidirectional-references
reference/complex-references
reference/indexes
reference/search-indexes
reference/inheritance-mapping
reference/embedded-mapping
reference/trees
Expand Down
91 changes: 91 additions & 0 deletions doctrine-mongo-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<xs:element name="lifecycle-callbacks" type="odm:lifecycle-callbacks" minOccurs="0" />
<xs:element name="also-load-methods" type="odm:also-load-methods" minOccurs="0" />
<xs:element name="indexes" type="odm:indexes" minOccurs="0" />
<xs:element name="search-indexes" type="odm:search-indexes" minOccurs="0" />
<xs:element name="shard-key" type="odm:shard-key" minOccurs="0" />
<xs:element name="read-preference" type="odm:read-preference" minOccurs="0" />
<xs:element name="schema-validation" type="odm:schema-validation" minOccurs="0" />
Expand Down Expand Up @@ -466,6 +467,96 @@
</xs:choice>
</xs:complexType>

<xs:complexType name="search-indexes">
<xs:choice maxOccurs="unbounded">
<xs:element name="search-index" type="odm:search-index" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>

<!-- https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/ -->
<xs:complexType name="search-index">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="odm:search-index-field" minOccurs="0" maxOccurs="unbounded" />
<!-- Note: custom analyzers are intentionally unsupported in XML -->
<xs:element name="synonym" type="odm:search-index-synonym" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="stored-source" type="odm:search-index-stored-source" minOccurs="0" maxOccurs="1" />
</xs:choice>

<xs:attribute name="name" type="xs:string" />
<xs:attribute name="dynamic" type="xs:boolean" />
<xs:attribute name="analyzer" type="xs:string" />
<xs:attribute name="searchAnalyzer" type="xs:string" />
<xs:attribute name="storedSource" type="xs:boolean" />
</xs:complexType>

<!-- https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/ -->
<xs:complexType name="search-index-field">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/document-type/ -->
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/embedded-documents-type/ -->
<xs:element name="field" type="odm:search-index-field" minOccurs="0" maxOccurs="unbounded" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/string-type/ -->
<xs:element name="multi" type="odm:search-index-field" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>

<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="type" type="xs:string" use="required" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/autocomplete-type/ -->
<xs:attribute name="maxGrams" type="xs:integer" />
<xs:attribute name="minGrams" type="xs:integer" />
<xs:attribute name="tokenization" type="xs:string" />
<xs:attribute name="foldDiacritics" type="xs:boolean" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/document-type/ -->
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/embedded-documents-type/ -->
<xs:attribute name="dynamic" type="xs:boolean" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/geo-type/ -->
<xs:attribute name="indexShapes" type="xs:boolean" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/knn-vector/ -->
<xs:attribute name="dimensions" type="xs:integer" />
<xs:attribute name="similarity" type="xs:string" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/number-type/ -->
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/number-facet-type/ -->
<xs:attribute name="representation" type="xs:string" />
<xs:attribute name="indexIntegers" type="xs:boolean" />
<xs:attribute name="indexDoubles" type="xs:boolean" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/string-type/ -->
<xs:attribute name="analyzer" type="xs:string" />
<xs:attribute name="searchAnalyzer" type="xs:string" />
<xs:attribute name="indexOptions" type="xs:string" />
<xs:attribute name="store" type="xs:boolean" />
<xs:attribute name="ignoreAbove" type="xs:integer" />
<xs:attribute name="norms" type="xs:string" />
<!-- https://www.mongodb.com/docs/atlas/atlas-search/field-types/token-type/ -->
<xs:attribute name="normalizer" type="xs:string" />
</xs:complexType>

<!-- https://www.mongodb.com/docs/atlas/atlas-search/synonyms/ -->
<xs:complexType name="search-index-synonym">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="analyzer" type="xs:string" use="required" />
<xs:attribute name="sourceCollection" type="xs:string" use="required" />
</xs:complexType>

<!-- https://www.mongodb.com/docs/atlas/atlas-search/stored-source-definition -->
<xs:complexType name="search-index-stored-source">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="odm:search-index-stored-source-field" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>

<xs:attribute name="type" type="odm:search-index-stored-source-type" use="required" />
</xs:complexType>

<xs:complexType name="search-index-stored-source-field">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>

<xs:simpleType name="search-index-stored-source-type">
<xs:restriction base="xs:token">
<xs:enumeration value="include" />
<xs:enumeration value="exclude" />
</xs:restriction>
</xs:simpleType>

<xs:complexType name="shard-key">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="key" type="odm:shard-key-key" maxOccurs="unbounded" />
Expand Down
Loading
Loading