Merge remote-tracking branch 'upstream/master' into compat4/btasker

This commit is contained in:
Robbie Averill 2017-11-14 16:31:16 +13:00
commit 0de6f52b1f
16 changed files with 247 additions and 31 deletions

View File

@ -1,4 +1,4 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details # See https://github.com/silverstripe/silverstripe-travis-support for setup details
language: php language: php
@ -16,8 +16,6 @@ matrix:
include: include:
- php: 5.6 - php: 5.6
env: DB=MYSQL CORE_RELEASE=4 env: DB=MYSQL CORE_RELEASE=4
- php: 5.6
# env: DB=MYSQL CORE_RELEASE=4 SUBSITES=1
- php: 5.6 - php: 5.6
env: DB=MYSQL CORE_RELEASE=4 QUEUEDJOBS=1 env: DB=MYSQL CORE_RELEASE=4 QUEUEDJOBS=1
- php: 7.0 - php: 7.0
@ -35,7 +33,7 @@ matrix:
before_script: before_script:
- composer self-update || true - composer self-update || true
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support - git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support
- "if [ \"$SUBSITES\" = \"\" -a \"$QUEUEDJOBS\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi" - "if [ \"$SUBSITES\" = \"\" -a \"$QUEUEDJOBS\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
# - "if [ \"$SUBSITES\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/subsites; fi" # - "if [ \"$SUBSITES\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/subsites; fi"
- "if [ \"$QUEUEDJOBS\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/queuedjobs; fi" - "if [ \"$QUEUEDJOBS\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/queuedjobs; fi"

View File

@ -51,6 +51,27 @@ abstract class SearchIndex extends ViewableData
*/ */
private static $hide_ancestor; private static $hide_ancestor;
/**
* Used to separate class name and relation name in the sources array
* this string must not be present in class name
* @var string
* @config
*/
private static $class_delimiter = '_|_';
/**
* This is used to clean the source name from suffix
* suffixes are needed to support multiple relations with the same name on different page types
* @param string $source
* @return string
*/
protected function getSourceName($source)
{
$source = explode(self::config()->get('class_delimiter'), $source);
return $source[0];
}
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
@ -71,6 +92,9 @@ abstract class SearchIndex extends ViewableData
/** /**
* Examines the classes this index is built on to try and find defined fields in the class hierarchy for those classes. * Examines the classes this index is built on to try and find defined fields in the class hierarchy for those classes.
* Looks for db and viewable-data fields, although can't nessecarily find type for viewable-data fields. * Looks for db and viewable-data fields, although can't nessecarily find type for viewable-data fields.
* If multiple classes have a relation with the same name all of these will be included in the search index
* Note that only classes that have the relations uninherited (defined in them) will be listed
* this is because inherited relations do not need to be processed by index explicitly
*/ */
public function fieldData($field, $forceType = null, $extraOptions = array()) public function fieldData($field, $forceType = null, $extraOptions = array())
{ {
@ -91,21 +115,37 @@ abstract class SearchIndex extends ViewableData
foreach ($lookups as $lookup) { foreach ($lookups as $lookup) {
$next = array(); $next = array();
foreach ($sources as $source => $options) { foreach ($sources as $source => $baseOptions) {
$class = null; $source = $this->getSourceName($source);
foreach (SearchIntrospection::hierarchy($source, $options['include_children']) as $dataclass) { foreach (SearchIntrospection::hierarchy($source, $baseOptions['include_children']) as $dataclass) {
$class = null;
$options = $baseOptions;
$singleton = singleton($dataclass); $singleton = singleton($dataclass);
$schema = DataObject::getSchema(); $schema = DataObject::getSchema();
$className = $singleton->getClassName(); $className = $singleton->getClassName();
if ($hasOne = $schema->hasOneComponent($className, $lookup)) { if ($hasOne = $schema->hasOneComponent($className, $lookup)) {
// we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'has_one', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList)) {
continue;
}
$class = $hasOne; $class = $hasOne;
$options['lookup_chain'][] = array( $options['lookup_chain'][] = array(
'call' => 'method', 'method' => $lookup, 'call' => 'method', 'method' => $lookup,
'through' => 'has_one', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => "{$lookup}ID" 'through' => 'has_one', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => "{$lookup}ID"
); );
} elseif ($hasMany = $schema->hasManyComponent($className, $lookup)) { } elseif ($hasMany = $schema->hasManyComponent($className, $lookup)) {
// we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'has_many', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList)) {
continue;
}
$class = $hasMany; $class = $hasMany;
$options['multi_valued'] = true; $options['multi_valued'] = true;
$options['lookup_chain'][] = array( $options['lookup_chain'][] = array(
@ -113,6 +153,13 @@ abstract class SearchIndex extends ViewableData
'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $schema->getRemoteJoinField($className, $lookup, 'has_many') 'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $schema->getRemoteJoinField($className, $lookup, 'has_many')
); );
} elseif ($manyMany = $schema->manyManyComponent($className, $lookup)) { } elseif ($manyMany = $schema->manyManyComponent($className, $lookup)) {
// we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'many_many', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList)) {
continue;
}
$class = $manyMany[2]; $class = $manyMany[2];
$options['multi_valued'] = true; $options['multi_valued'] = true;
$options['lookup_chain'][] = array( $options['lookup_chain'][] = array(
@ -125,8 +172,10 @@ abstract class SearchIndex extends ViewableData
if (!isset($options['origin'])) { if (!isset($options['origin'])) {
$options['origin'] = $dataclass; $options['origin'] = $dataclass;
} }
$next[$class] = $options;
continue 2; // we add suffix here to prevent the relation to be overwritten by other instances
// all sources lookups must clean the source name before reading it via getSourceName()
$next[$class . self::config()->get('class_delimiter') . $dataclass] = $options;
} }
} }
} }
@ -139,6 +188,7 @@ abstract class SearchIndex extends ViewableData
} }
foreach ($sources as $class => $options) { foreach ($sources as $class => $options) {
$class = $this->getSourceName($class);
$dataclasses = SearchIntrospection::hierarchy($class, $options['include_children']); $dataclasses = SearchIntrospection::hierarchy($class, $options['include_children']);
while (count($dataclasses)) { while (count($dataclasses)) {

View File

@ -60,7 +60,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
* *
* @var array * @var array
*/ */
public static $dirty_indexes = true; public static $dirty_indexes = array();
/** /**
* If solrindex::commit has already been performed, but additional commits are necessary, * If solrindex::commit has already been performed, but additional commits are necessary,

View File

@ -30,6 +30,10 @@ class Solr_Configure extends Solr_BuildTask
->error("Failure: " . $e->getMessage()); ->error("Failure: " . $e->getMessage());
} }
} }
if (isset($e)) {
exit(1);
}
} }
/** /**
@ -87,4 +91,3 @@ class Solr_Configure extends Solr_BuildTask
} }
} }
} }

View File

@ -29,7 +29,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.x-dev" "dev-master": "3.x-dev"
} }
}, },
"suggest": { "suggest": {

View File

@ -140,7 +140,6 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -149,6 +148,7 @@
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.PorterStemFilterFactory"/> <filter class="solr.PorterStemFilterFactory"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -166,11 +166,11 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.PorterStemFilterFactory"/> <filter class="solr.PorterStemFilterFactory"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -180,7 +180,6 @@
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" > <fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
<analyzer> <analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
@ -189,6 +188,7 @@
<!-- this filter can remove any duplicate tokens that appear at the same position - sometimes <!-- this filter can remove any duplicate tokens that appear at the same position - sometimes
possible with WordDelimiterFilter in conjuncton with stemming. --> possible with WordDelimiterFilter in conjuncton with stemming. -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -214,7 +214,6 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -222,6 +221,7 @@
/> />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -240,7 +240,6 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -248,6 +247,7 @@
/> />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>

View File

@ -140,7 +140,6 @@
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.KeywordRepeatFilterFactory"/> <filter class="solr.KeywordRepeatFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -150,6 +149,7 @@
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -166,12 +166,12 @@
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.KeywordRepeatFilterFactory"/> <filter class="solr.KeywordRepeatFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -180,7 +180,6 @@
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" > <fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
<analyzer> <analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
@ -189,6 +188,7 @@
<!-- this filter can remove any duplicate tokens that appear at the same position - sometimes <!-- this filter can remove any duplicate tokens that appear at the same position - sometimes
possible with WordDelimiterFilter in conjuncton with stemming. --> possible with WordDelimiterFilter in conjuncton with stemming. -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -225,7 +225,6 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -233,6 +232,7 @@
/> />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>
@ -251,7 +251,6 @@
</analyzer> </analyzer>
<analyzer type="query"> <analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" <filter class="solr.StopFilterFactory"
ignoreCase="true" ignoreCase="true"
words="stopwords.txt" words="stopwords.txt"
@ -259,6 +258,7 @@
/> />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
</analyzer> </analyzer>
</fieldType> </fieldType>

View File

@ -203,6 +203,20 @@ To allow searches on words containing numeric tokens, you'll need to update your
Update your index to point to your overloaded template using the method described above. Update your index to point to your overloaded template using the method described above.
#### Searching for macrons and other Unicode characters
The “ASCIIFoldingFilterFactory” filter converts alphabetic, numeric, and symbolic Unicode characters which are not in the Basic Latin Unicode block (the first 127 ASCII characters) to their ASCII equivalents, if one exists.
Find the fields in your overloaded `types.ss` that you want to enable this behaviour in. EG:
<fieldType name="htmltext" class="solr.TextField" ... >
Add the following to both its index analyzer and query analyzer records.
<filter class="solr.ASCIIFoldingFilterFactory"/>
Update your index to point to your overloaded template using the method described above.
### Spell Checking ("Did you mean...") ### Spell Checking ("Did you mean...")
Solr has various spell checking strategies (see the ["SpellCheckComponent" docs](http://wiki.apache.org/solr/SpellCheckComponent)), all of which are configured through `solrconfig.xml`. Solr has various spell checking strategies (see the ["SpellCheckComponent" docs](http://wiki.apache.org/solr/SpellCheckComponent)), all of which are configured through `solrconfig.xml`.

View File

@ -0,0 +1,13 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
/**
* Used to test inherited ambiguous relationships.
*/
class SearchUpdaterTest_ExtendedContainer extends SearchUpdaterTest_OtherContainer
{
private static $db = array(
'SomeField' => 'Varchar',
);
}

View File

@ -15,6 +15,7 @@ class SearchUpdaterTest_HasMany extends DataObject
private static $table_name = 'SearchUpdaterTest_HasMany'; private static $table_name = 'SearchUpdaterTest_HasMany';
private static $has_one = array( private static $has_one = array(
'HasManyContainer' => SearchUpdaterTest_Container::class 'HasManyContainer' => SearchUpdaterTest_Container::class,
'HasManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
); );
} }

View File

@ -15,6 +15,7 @@ class SearchUpdaterTest_HasOne extends DataObject
private static $table_name = 'SearchUpdaterTest_HasOne'; private static $table_name = 'SearchUpdaterTest_HasOne';
private static $has_many = array( private static $has_many = array(
'HasManyContainers' => SearchUpdaterTest_Container::class 'HasManyContainers' => SearchUpdaterTest_Container::class,
'HasManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
); );
} }

View File

@ -3,7 +3,6 @@
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest; namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
class SearchUpdaterTest_ManyMany extends DataObject class SearchUpdaterTest_ManyMany extends DataObject
{ {
@ -15,6 +14,7 @@ class SearchUpdaterTest_ManyMany extends DataObject
private static $table_name = 'SearchUpdaterTest_ManyMany'; private static $table_name = 'SearchUpdaterTest_ManyMany';
private static $belongs_many_many = array( private static $belongs_many_many = array(
'ManyManyContainer' => SearchUpdaterTest_Container::class 'ManyManyContainer' => SearchUpdaterTest_Container::class,
'ManyManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
); );
} }

View File

@ -0,0 +1,19 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
use SilverStripe\ORM\DataObject;
/**
* Used to test ambiguous relationships.
*/
class SearchUpdaterTest_OtherContainer extends DataObject
{
private static $has_many = [
'HasManyObjects' => SearchUpdaterTest_HasMany::class,
];
private static $many_many = [
'ManyManyObjects' => SearchUpdaterTest_ManyMany::class,
];
}

View File

@ -1,6 +1,8 @@
<?php <?php
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationInheritedIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex2; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex2;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex;
@ -9,6 +11,7 @@ use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Contai
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_OtherContainer;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
@ -50,6 +53,64 @@ class SolrIndexTest extends SapphireTest
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $data['class']); $this->assertEquals(SearchUpdaterTest_ManyMany::class, $data['class']);
} }
public function testFieldDataAmbiguousHasMany()
{
$index = new SolrIndexTest_AmbiguousRelationIndex();
$data = $index->fieldData('HasManyObjects.Field1');
$this->assertArrayHasKey('SearchUpdaterTest_Container_HasManyObjects_Field1', $data);
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_HasManyObjects_Field1', $data);
$dataContainer = $data['SearchUpdaterTest_Container_HasManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
$this->assertEquals(SearchUpdaterTest_HasMany::class, $dataContainer['class']);
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_HasManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
$this->assertEquals(SearchUpdaterTest_HasMany::class, $dataOtherContainer['class']);
}
public function testFieldDataAmbiguousManyMany()
{
$index = new SolrIndexTest_AmbiguousRelationIndex();
$data = $index->fieldData('ManyManyObjects.Field1');
$this->assertArrayHasKey('SearchUpdaterTest_Container_ManyManyObjects_Field1', $data);
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1', $data);
$dataContainer = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataContainer['class']);
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataOtherContainer['class']);
}
public function testFieldDataAmbiguousManyManyInherited()
{
$index = new SolrIndexTest_AmbiguousRelationInheritedIndex();
$data = $index->fieldData('ManyManyObjects.Field1');
$this->assertArrayHasKey('SearchUpdaterTest_Container_ManyManyObjects_Field1', $data);
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1', $data);
$this->assertArrayNotHasKey('SearchUpdaterTest_ExtendedContainer_ManyManyObjects_Field1', $data);
$dataContainer = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataContainer['class']);
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1'];
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataOtherContainer['class']);
}
/** /**
* Test boosting on SearchQuery * Test boosting on SearchQuery
*/ */
@ -62,7 +123,8 @@ class SolrIndexTest extends SapphireTest
$serviceMock->expects($this->once()) $serviceMock->expects($this->once())
->method('search') ->method('search')
->with($this->equalTo('+(Field1:term^1.5 OR HasOneObject_Field1:term^3)'), ->with(
$this->equalTo('+(Field1:term^1.5 OR HasOneObject_Field1:term^3)'),
$this->anything(), $this->anything(),
$this->anything(), $this->anything(),
$this->anything(), $this->anything(),
@ -93,7 +155,8 @@ class SolrIndexTest extends SapphireTest
$serviceMock->expects($this->once()) $serviceMock->expects($this->once())
->method('search') ->method('search')
->with($this->equalTo('+term'), ->with(
$this->equalTo('+term'),
$this->anything(), $this->anything(),
$this->anything(), $this->anything(),
$this->equalTo(['qf' => SearchUpdaterTest_Container::class . '_Field1^1.5 ' . SearchUpdaterTest_Container::class . '_Field2^2.1 _text', $this->equalTo(['qf' => SearchUpdaterTest_Container::class . '_Field1^1.5 ' . SearchUpdaterTest_Container::class . '_Field2^2.1 _text',

View File

@ -0,0 +1,26 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SolrIndexTest;
use SilverStripe\FullTextSearch\Solr\SolrIndex;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_OtherContainer;
class SolrIndexTest_AmbiguousRelationIndex extends SolrIndex
{
protected function getStoredDefault()
{
// Override isDev defaulting to stored
return 'false';
}
public function init()
{
$this->addClass(SearchUpdaterTest_Container::class);
$this->addClass(SearchUpdaterTest_OtherContainer::class);
// These relationships exist on both classes
$this->addFilterField('HasManyObjects.Field1');
$this->addFilterField('ManyManyObjects.Field1');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SolrIndexTest;
use SilverStripe\FullTextSearch\Solr\SolrIndex;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ExtendedContainer;
class SolrIndexTest_AmbiguousRelationInheritedIndex extends SolrIndex
{
protected function getStoredDefault()
{
// Override isDev defaulting to stored
return 'false';
}
public function init()
{
$this->addClass(SearchUpdaterTest_Container::class);
// this one has not the relation defined in it's class but is rather inherited from parent
// note that even if we do not include it's parent class the fields will be properly added
$this->addClass(SearchUpdaterTest_ExtendedContainer::class);
// These relationships exist on both classes
$this->addFilterField('HasManyObjects.Field1');
$this->addFilterField('ManyManyObjects.Field1');
}
}