Merge pull request #9454 from open-sausages/pulls/4/myisam

NEW Allow InnoDB for FULLTEXT indexes
This commit is contained in:
Daniel Hensby 2020-04-14 11:50:45 +01:00 committed by GitHub
commit 03239f9dcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 43 deletions

View File

@ -0,0 +1,55 @@
# 4.6.0 (Unreleased)
## Overview {#overview}
* [MySQL tables are auto-converted from MyISAM to InnoDB](#myisam)
## MySQL tables are auto-converted from MyISAM to InnoDB {#myisam}
Beginning with [4.4.0](https://docs.silverstripe.org/en/4/changelogs/4.4.0/),
our minimum requirement for MySQL is 5.6 (since MySQL 5.5 end of life reached in December 2018).
Starting with MySQL 5.6, [InnoDB](https://dev.mysql.com/doc/refman/5.6/en/innodb-introduction.html)
is the new default storage engine,
replacing the older [MyISAM](https://dev.mysql.com/doc/refman/5.6/en/myisam-storage-engine.html) engine.
Silverstripe CMS already creates InnoDB tables by default,
mainly in order to benefit from their better support for database transactions.
Before MySQL 5.6, InnoDB didn't have a `FULLTEXT` search index,
requiring us to enforce the MyISAM engine when devs opted into this index type
in their particular setup. There are a few ways in which this opt-in can happen:
* Adding the [FulltextSearchable](https://github.com/silverstripe/silverstripe-framework/blob/4/src/ORM/Search/FulltextSearchable.php)
extension to a DataObject, as described in our
[search docs](https://docs.silverstripe.org/en/4/developer_guides/search/fulltextsearch/)
* Defining `'type' => 'fulltext'` in `DataObject::$db` column definitions
* Implementing [DBIndexable](https://github.com/silverstripe/silverstripe-framework/blob/4/src/ORM/FieldType/DBIndexable.php)
on a custom `DBField` subclass.
* Setting `'ENGINE=MyISAM'` in `DataObject::$create_table_options`
This search index is not required to enable simple text search
in the "Pages" section of the CMS, or any ModelAdmin implementations.
We generally recommend to choose a more powerful
[search addon](https://addons.silverstripe.org/add-ons?search=fulltext&type=&sort=downloads)
(e.g. based on Solr or ElasticSearch) for website frontend search use cases.
As of 4.6.0, a `dev/build` will automatically switch MyISAM tables to InnoDB,
which automatically recreates any indexes required. If you have large indexes,
this can extend the duration if this task. As usual, back up your database
before upgrading, and test upgrades on non-production systems first.
Our tests indicate that indexes with thousands of records and screen pages
worth of content (15MB index size) are converted in a few seconds.
In order to opt out of this change, you can set the engine explicitly
for your DataObject implementations:
```php
use SilverStripe\ORM\Connect\MySQLSchemaManager;
use SilverStripe\ORM\DataObject;
class MyDataObject extends DataObject
{
private static $create_table_options = [
MySQLSchemaManager::ID => 'ENGINE=MyISAM'
];
}
```

View File

@ -144,6 +144,12 @@ class MySQLDatabase extends Database implements TransactionManager
* The core search engine, used by this class and its subclasses to do fun stuff. * The core search engine, used by this class and its subclasses to do fun stuff.
* Searches both SiteTree and File. * Searches both SiteTree and File.
* *
* Caution: While the $keywords argument is escaped for safe use in a query context,
* you need to ensure that it is also a valid boolean expression when opting into $booleanSearch.
* For example, the "asterisk" and "greater than" characters have a special meaning in this context,
* and can only be placed in certain parts of the keywords. You will need to preprocess and sanitise
* user input accordingly in order to avoid query errors.
*
* @param array $classesToSearch * @param array $classesToSearch
* @param string $keywords Keywords as a string. * @param string $keywords Keywords as a string.
* @param int $start * @param int $start
@ -221,8 +227,9 @@ class MySQLDatabase extends Database implements TransactionManager
$match[$fileClass] = "MATCH (Name, Title) AGAINST ('$keywords' $boolean) AND ClassName = '$fileClassSQL'"; $match[$fileClass] = "MATCH (Name, Title) AGAINST ('$keywords' $boolean) AND ClassName = '$fileClassSQL'";
// We make the relevance search by converting a boolean mode search into a normal one // We make the relevance search by converting a boolean mode search into a normal one
$relevanceKeywords = str_replace(array('*', '+', '-'), '', $keywords); $booleanChars = ['*', '+', '@', '-', '(', ')', '<', '>'];
$htmlEntityRelevanceKeywords = str_replace(array('*', '+', '-'), '', $htmlEntityKeywords); $relevanceKeywords = str_replace($booleanChars, '', $keywords);
$htmlEntityRelevanceKeywords = str_replace($booleanChars, '', $htmlEntityKeywords);
$relevance[$pageClass] = "MATCH (Title, MenuTitle, Content, MetaDescription) " $relevance[$pageClass] = "MATCH (Title, MenuTitle, Content, MetaDescription) "
. "AGAINST ('$relevanceKeywords') " . "AGAINST ('$relevanceKeywords') "
. "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityRelevanceKeywords')"; . "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityRelevanceKeywords')";

View File

@ -40,10 +40,6 @@ class MySQLSchemaManager extends DBSchemaManager
} }
if ($indexes) { if ($indexes) {
foreach ($indexes as $k => $v) { foreach ($indexes as $k => $v) {
// force MyISAM if we have a fulltext index
if ($v['type'] === 'fulltext') {
$addOptions = 'ENGINE=MyISAM';
}
$indexSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n"; $indexSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n";
} }
} }
@ -104,30 +100,11 @@ class MySQLSchemaManager extends DBSchemaManager
$dbID = self::ID; $dbID = self::ID;
if ($alteredOptions && isset($alteredOptions[$dbID])) { if ($alteredOptions && isset($alteredOptions[$dbID])) {
$indexList = $this->indexList($tableName); $this->query(sprintf("ALTER TABLE \"%s\" %s", $tableName, $alteredOptions[$dbID]));
$skip = false; $this->alterationMessage(
foreach ($indexList as $index) { sprintf("Table %s options changed: %s", $tableName, $alteredOptions[$dbID]),
if ($index['type'] === 'fulltext') { "changed"
$skip = true; );
break;
}
}
if ($skip) {
$this->alterationMessage(
sprintf(
"Table %s options not changed to %s due to fulltextsearch index",
$tableName,
$alteredOptions[$dbID]
),
"changed"
);
} else {
$this->query(sprintf("ALTER TABLE \"%s\" %s", $tableName, $alteredOptions[$dbID]));
$this->alterationMessage(
sprintf("Table %s options changed: %s", $tableName, $alteredOptions[$dbID]),
"changed"
);
}
} }
$alterations = implode(",\n", $alterList); $alterations = implode(",\n", $alterList);

View File

@ -2,7 +2,6 @@
namespace SilverStripe\ORM\Filters; namespace SilverStripe\ORM\Filters;
use SilverStripe\Core\Convert;
use SilverStripe\ORM\DataQuery; use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use Exception; use Exception;
@ -10,8 +9,7 @@ use Exception;
/** /**
* Filters by full-text matching on the given field. * Filters by full-text matching on the given field.
* *
* Full-text indexes are only available with MyISAM tables. The following column types are * The following column types are supported:
* supported:
* - Char * - Char
* - Varchar * - Varchar
* - Text * - Text

View File

@ -25,12 +25,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest
// Start tests // Start tests
static::start(); static::start();
// enable fulltext option on this table
TestIndexObject::config()->update(
'create_table_options',
array(MySQLSchemaManager::ID => 'ENGINE=MyISAM')
);
parent::setUpBeforeClass(); parent::setUpBeforeClass();
} }

View File

@ -34,8 +34,4 @@ class TestObject extends DataObject implements TestOnly
'columns' => ['ColumnE'], 'columns' => ['ColumnE'],
], ],
); );
private static $create_table_options = array(
MySQLSchemaManager::ID => "ENGINE=MyISAM",
);
} }