From d54bab7761a9fa4d1e7455bb1c90d03cd9711965 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 3 Jun 2025 14:09:05 +1200 Subject: [PATCH] DOC Document default_sort composite indexes --- en/02_Developer_Guides/00_Model/12_Indexes.md | 111 ++++++++++++++++-- en/08_Changelogs/6.1.0.md | 10 ++ 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/en/02_Developer_Guides/00_Model/12_Indexes.md b/en/02_Developer_Guides/00_Model/12_Indexes.md index 1ea52c732..3ee2809d9 100644 --- a/en/02_Developer_Guides/00_Model/12_Indexes.md +++ b/en/02_Developer_Guides/00_Model/12_Indexes.md @@ -25,7 +25,8 @@ The Silverstripe CMS framework already places certain indexes for you by default - The `ClassName` column if your model inherits from `DataObject` - All relationships defined in the model have indexes for their `has_one` entity (for `many_many` relationships this index is present on the associative entity). -- All fields used in `default_sort` configuration +- All fields used in `default_sort` configuration (see [`default_sort` index mode](#default-sort-index-mode) below for additional options) +- Some built-in models (such as [`Member`](api:SilverStripe\Security\Member)) have specific indexes added ## Defining an index @@ -39,7 +40,7 @@ use SilverStripe\ORM\DataObject; class MyObject extends DataObject { - private static $indexes = [ + private static array $indexes = [ '' => true, '' => [ 'type' => '', @@ -74,12 +75,12 @@ use SilverStripe\ORM\DataObject; class MyTestObject extends DataObject { - private static $db = [ + private static array $db = [ 'MyField' => 'Varchar', 'MyOtherField' => 'Varchar', ]; - private static $indexes = [ + private static array $indexes = [ 'MyIndexName' => ['MyField', 'MyOtherField'], ]; } @@ -90,21 +91,113 @@ class MyTestObject extends DataObject For complex queries it may be necessary to define a complex or composite index on the supporting object. To create a composite index, define the fields in the index order as a comma separated list. -- index (col1) - `WHERE col1 = ?` -- index (col1, col2) = `WHERE (col1 = ? AND col2 = ?)` -- index (col1, col2, col3) = `WHERE (col1 = ? AND col2 = ? AND col3 = ?)` +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; + +class MyTestObject extends DataObject +{ + // ... + private static array $indexes = [ + 'MyCompositeIndex' => ['MyField', 'MyOtherField', 'MyThirdField'], + ]; +} +``` + +Composite indexes can be used for any query that uses the fields in this specific order, include queries that omit columns from later in the index. For example, the index would be used for these queries: -The index would not be used for a query `WHERE col2 = ?` or for `WHERE col1 = ? OR col2 = ?` +- `WHERE MyField = ?` +- `WHERE (MyField = ? AND MyOtherField = ?)` +- `WHERE (MyField = ? AND MyOtherField = ? AND MyThirdField = ?)` + +The index would not be used for a query `WHERE MyOtherField = ?` (because the first column is not used in this query) or for `WHERE MyField = ? OR MyOtherField = ?` (because the database has to check all rows to see if the `MyOtherField` column matches on its own). As an alternative to a composite index, you can also create a hashed column which is a combination of information from other columns. If this is indexed, smaller and reasonably unique it might be faster that an index on the whole column. +### Directionality + +You can choose to define the direction for each column in the index. This can be useful if you know you usually sort a model in a specific direction. It's crucial for composite indexes if you are sorting on one column in a different direction than the others. + +The direction is defined by adding either `ASC` or `DESC` after the column name. `ASC` is implied if no direction is specified. + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; + +class MyTestObject extends DataObject +{ + // ... + private static array $indexes = [ + 'MyCompositeIndex' => ['MyField ASC', 'MyOtherField DESC'], + ]; +} +``` + +### `default_sort` index mode {#default-sort-index-mode} + +By default, if the [`default_sort`](api:SilverStripe\ORM\DataObject->default_sort) configuration for your `DataObject` subclass specifies multiple columns, the ORM will create an index for each column as well as a composite index that includes all of the columns in your sort order. The composite index is always called `default_sort_composite`. + +You can change what indexes will be created by setting the [`default_sort_index_mode`](api:SilverStripe\ORM\DataObject->default_sort_index_mode) configuration for your class. It takes the following options: + +|Constant|Value|What it does| +|---|---|---| +|[`DataObjectSchema::SORT_INDEX_MODE_NONE`](api:SilverStripe\ORM\DataObjectSchema::SORT_INDEX_MODE_NONE)|none|Do not create any indexes for `default_sort`. Implies you will define your own indexes.| +|[`DataObjectSchema::SORT_INDEX_MODE_SINGLE`](api:SilverStripe\ORM\DataObjectSchema::SORT_INDEX_MODE_SINGLE)|single|Do not create a composite index, only create an index for each column.| +|[`DataObjectSchema::SORT_INDEX_MODE_COMPOSITE`](api:SilverStripe\ORM\DataObjectSchema::SORT_INDEX_MODE_COMPOSITE)|composite|Create only a composite index, not an index for each column.| +|[`DataObjectSchema::SORT_INDEX_MODE_BOTH`](api:SilverStripe\ORM\DataObjectSchema::SORT_INDEX_MODE_BOTH)|both|Create both a composite index and an index for each column (this is the default).| + +> [!WARNING] +> Note that the ORM cannot create composite indexes that include columns in a different table (e.g. from a parent class or a relation). + +If you have the following classes: + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; + +class MyTestObject extends DataObject +{ + private static array $db = [ + 'MyField' => 'Varchar', + 'MyOtherField' => 'Varchar', + ]; + + private static array $default_sort = 'ID'; +} +``` + +```php +namespace App\Model; + +class MySubclass extends MyTestObject +{ + private static array $db = [ + 'MyThirdField' => 'Varchar', + ]; + + private static array $default_sort = 'MyField, MyOtherField, MyThirdField'; +} +``` + +The ORM will not create a composite index for either class. It doesn't create one for `MyTestObject` because its `default_sort` definition only includes one column, and it doesn't create one for `MySubclass` because the first two columns it references belong on the superclass table, not the subclass table. + +> [!NOTE] +> If you set `default_sort_index_mode` to "Composite" but your `default_sort` configuration only contains a single column, a `default_sort_composite` index will be created, though it will only contain that single column. + +In these situations you may want to create your own composite index - for this example it would be an index with the first two columns in the `MyTestObject` table, as it is likely this index would be used when sorting `MySubclass` records. + +Note that `ID` as a column at the *end* of your sort order won't be included in any composite indexes, because the database already implicitly adds that to all indexes. + ## Index creation/destruction Indexes are generated and removed automatically when building the database based on your configuration. Caution if you're working with large tables and modify an index as the next time the database is built it will `DROP` the index, and then `ADD` it. -Note that the ORM won't automatically drop indexes if you remove them from the `indexes` configuration array. Instead, you need to set the value to `false` like so: +Note that the ORM won't automatically drop indexes if you remove them from the `indexes` configuration array or update your `default_sort` configuration. Instead, you need to set the value to `false` like so: ```php namespace App\Model; diff --git a/en/08_Changelogs/6.1.0.md b/en/08_Changelogs/6.1.0.md index 978e2f798..19b5fe7ad 100644 --- a/en/08_Changelogs/6.1.0.md +++ b/en/08_Changelogs/6.1.0.md @@ -32,6 +32,16 @@ To remedy this, we've made the following changes: If you are using a different adapter that might support globbing such as using FTP (for specific FTP servers), consider implementing the `GlobContentLister` interface. If you use your own implementation of [`FileIDHelper`](api:SilverStripe\Assets\FilenameParsing\FileIDHelper) consider also implementing the `GlobbableFileIDHelper` interface. +#### Composite indexes for `default_sort` {#sort-indexes} + +The ORM automatically creates an index for each column in the [`default_sort`](api:SilverStripe\ORM\DataObject->default_sort) configuration for your `DataObject` subclasses. This index might only be useful for part of the sort, or in some cases ignored entirely if the database server decides it will be faster to just traverse the whole table itself. + +To remedy this, a new [`DataObject.default_sort_index_mode`](api:SilverStripe\ORM\DataObject->default_sort_index_mode) configuration property has been added, which by default will tell the ORM to create a composite index (if you're sorting by multiple fields) in addition to the individual column indexes it was already making. + +When created, the composite index will always be called `default_sort_composite`. + +There are some caveats to this, along with some other options you might want to set, so check out the [indexes documentation](/developer_guides/model/indexes/) for more details. + ### Drop indexes in `indexes` configuration {#drop-indexes} If you define some database index in the [`indexes`](api:SilverStripe\ORM\DataObject->indexes) configuration for a `DataObject` model and then remove that index from the configuration, the ORM won't drop that index for you. This means you can have indexes taking up space in your database that you don't want anymore.