mirror of
https://github.com/silverstripe/silverstripe-fulltextsearch
synced 2024-10-22 14:05:29 +02:00
Merge pull request #141 from silverstripe-elliot/compat4/btasker
WIP: Compat4/btasker
This commit is contained in:
commit
69f1d9bafe
43
.travis.yml
43
.travis.yml
@ -1,31 +1,34 @@
|
|||||||
# See https://github.com/silverstripe/silverstripe-travis-support for setup details
|
|
||||||
|
|
||||||
language: php
|
language: php
|
||||||
|
|
||||||
sudo: false
|
env:
|
||||||
|
global:
|
||||||
|
- COMPOSER_ROOT_VERSION="3.0.x-dev"
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- php: 5.4
|
|
||||||
env: DB=PGSQL CORE_RELEASE=3.1
|
|
||||||
- php: 5.5
|
|
||||||
env: DB=MYSQL CORE_RELEASE=3.2
|
|
||||||
- php: 5.6
|
- php: 5.6
|
||||||
env: DB=MYSQL CORE_RELEASE=3.3 SUBSITES=1
|
env: DB=MYSQL PHPCS_TEST=1 PHPUNIT_TEST=1
|
||||||
- php: 5.6
|
- php: 7.0
|
||||||
env: DB=MYSQL CORE_RELEASE=3.3 QUEUEDJOBS=1
|
env: DB=MYSQL PHPUNIT_TEST=1
|
||||||
- php: 7.1
|
- php: 7.1
|
||||||
env: DB=MYSQL CORE_RELEASE=3.6
|
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1
|
||||||
- php: 7.1
|
- php: 7.2
|
||||||
env: DB=MYSQL CORE_RELEASE=3
|
env: DB=MYSQL PHPUNIT_TEST=1
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- composer self-update || true
|
- phpenv rehash
|
||||||
- git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support
|
- phpenv config-rm xdebug.ini
|
||||||
- "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"
|
- composer validate
|
||||||
- "if [ \"$QUEUEDJOBS\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/queuedjobs; fi"
|
- composer require --no-update symbiote/silverstripe-queuedjobs ^4.0
|
||||||
- cd ~/builds/ss
|
# todo: Add Subsites in
|
||||||
|
- composer require --no-update silverstripe/installer 4.0.x-dev
|
||||||
|
- composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- vendor/bin/phpunit fulltextsearch/tests/
|
- if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit; fi
|
||||||
|
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi
|
||||||
|
- if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=vendor/silverstripe/framework/phpcs.xml.dist code tests; fi
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
global $databaseConfig;
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
if (isset($databaseConfig['type'])) SearchUpdater::bind_manipulation_capture();
|
|
||||||
|
|
||||||
Deprecation::notification_version('1.0.0', 'fulltextsearch');
|
if (isset($databaseConfig['type'])) {
|
||||||
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
RequestProcessor:
|
SilverStripe\Control\RequestProcessor:
|
||||||
properties:
|
properties:
|
||||||
filters:
|
filters:
|
||||||
- '%$SearchUpdater_BindManipulationCaptureFilter'
|
- '%$SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater_BindManipulationCaptureFilter'
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
DataObject:
|
SilverStripe\ORM\DataObject:
|
||||||
extensions:
|
extensions:
|
||||||
- 'SearchUpdater_ObjectHandler'
|
- 'SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater_ObjectHandler'
|
||||||
|
@ -1,34 +1,22 @@
|
|||||||
---
|
---
|
||||||
Name: defaultprocessor
|
Name: defaultprocessor
|
||||||
---
|
---
|
||||||
Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
SearchUpdateProcessor:
|
SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor:
|
||||||
class: SearchUpdateImmediateProcessor
|
class: SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor
|
||||||
SolrReindexHandler:
|
SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler:
|
||||||
class: SolrReindexImmediateHandler
|
class: SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexImmediateHandler
|
||||||
SearchLogFactory:
|
SilverStripe\FullTextSearch\Utils\Logging\SearchLogFactory:
|
||||||
class: 'MonologFactory'
|
class: SilverStripe\FullTextSearch\Utils\Logging\MonologFactory
|
||||||
---
|
|
||||||
Name: messagequeueprocessor
|
|
||||||
Only:
|
|
||||||
ModuleExists: messagequeue
|
|
||||||
Except:
|
|
||||||
Environment: 'dev'
|
|
||||||
---
|
|
||||||
Injector:
|
|
||||||
SearchUpdateProcessor:
|
|
||||||
class: SearchUpdateMessageQueueProcessor
|
|
||||||
SolrReindexHandler:
|
|
||||||
class: SolrReindexMessageHandler
|
|
||||||
---
|
---
|
||||||
Name: queuedjobprocessor
|
Name: queuedjobprocessor
|
||||||
Only:
|
Only:
|
||||||
ModuleExists: queuedjobs
|
ModuleExists: silverstripe/queuedjobs
|
||||||
Except:
|
Except:
|
||||||
Environment: 'dev'
|
Environment: 'dev'
|
||||||
---
|
---
|
||||||
Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
SearchUpdateProcessor:
|
SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor:
|
||||||
class: SearchUpdateQueuedJobProcessor
|
class: SilverStripe\FullTextSearch\Search\Processors\SearchUpdateQueuedJobProcessor
|
||||||
SolrReindexHandler:
|
SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler:
|
||||||
class: SolrReindexQueuedHandler
|
class: SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexQueuedHandler
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Search;
|
||||||
|
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class to manage active search indexes.
|
* Base class to manage active search indexes.
|
||||||
@ -37,7 +44,7 @@ class FullTextSearch
|
|||||||
if (self::$all_indexes === null) {
|
if (self::$all_indexes === null) {
|
||||||
// Get declared indexes, or otherwise default to all subclasses of SearchIndex
|
// Get declared indexes, or otherwise default to all subclasses of SearchIndex
|
||||||
$classes = Config::inst()->get(__CLASS__, 'indexes')
|
$classes = Config::inst()->get(__CLASS__, 'indexes')
|
||||||
?: ClassInfo::subclassesFor('SearchIndex');
|
?: ClassInfo::subclassesFor(SearchIndex::class);
|
||||||
|
|
||||||
$hidden = array();
|
$hidden = array();
|
||||||
$candidates = array();
|
$candidates = array();
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Search;
|
||||||
|
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some additional introspection tools that are used often by the fulltext search code
|
* Some additional introspection tools that are used often by the fulltext search code
|
||||||
@ -41,14 +45,14 @@ class SearchIntrospection
|
|||||||
$classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
|
$classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
|
||||||
}
|
}
|
||||||
|
|
||||||
$idx = array_search('DataObject', $classes);
|
$idx = array_search('SilverStripe\ORM\DataObject', $classes);
|
||||||
if ($idx !== false) {
|
if ($idx !== false) {
|
||||||
array_splice($classes, 0, $idx+1);
|
array_splice($classes, 0, $idx+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($dataOnly) {
|
if ($dataOnly) {
|
||||||
foreach ($classes as $i => $class) {
|
foreach ($classes as $i => $class) {
|
||||||
if (!DataObject::has_own_table($class)) {
|
if (!DataObject::getSchema()->classHasTable($class)) {
|
||||||
unset($classes[$i]);
|
unset($classes[$i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
|
|
||||||
class SearchUpdater_BindManipulationCaptureFilter implements RequestFilter
|
|
||||||
{
|
|
||||||
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model)
|
|
||||||
{
|
|
||||||
SearchUpdater::bind_manipulation_capture();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model)
|
|
||||||
{
|
|
||||||
/* NOP */
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Captures;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\Connect\MySQLDatabase;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
|
||||||
|
class SearchManipulateCapture_MySQLDatabase extends MySQLDatabase
|
||||||
|
{
|
||||||
|
|
||||||
|
public $isManipulationCapture = true;
|
||||||
|
|
||||||
|
public function manipulate($manipulation)
|
||||||
|
{
|
||||||
|
$res = parent::manipulate($manipulation);
|
||||||
|
SearchUpdater::handle_manipulation($manipulation);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Captures;
|
||||||
|
|
||||||
|
use SilverStripe\PostgreSQL\PostgreSQLDatabase;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
|
||||||
|
if (!class_exists('PostgreSQLDatabase')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchManipulateCapture_PostgreSQLDatabase extends PostgreSQLDatabase
|
||||||
|
{
|
||||||
|
|
||||||
|
public $isManipulationCapture = true;
|
||||||
|
|
||||||
|
public function manipulate($manipulation)
|
||||||
|
{
|
||||||
|
$res = parent::manipulate($manipulation);
|
||||||
|
SearchUpdater::handle_manipulation($manipulation);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Captures;
|
||||||
|
|
||||||
|
use SilverStripe\SQLite\SQLite3Database;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
|
||||||
|
if (!class_exists('SQLite3Database')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchManipulateCapture_SQLite3Database extends SQLite3Database
|
||||||
|
{
|
||||||
|
|
||||||
|
public $isManipulationCapture = true;
|
||||||
|
|
||||||
|
public function manipulate($manipulation)
|
||||||
|
{
|
||||||
|
$res = parent::manipulate($manipulation);
|
||||||
|
SearchUpdater::handle_manipulation($manipulation);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Indexes;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\View\ViewableData;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Utils\MultipleArrayIterator;
|
||||||
|
use SilverStripe\ORM\Queries\SQLSelect;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SearchIndex is the base index class. Each connector will provide a subclass of this that
|
* SearchIndex is the base index class. Each connector will provide a subclass of this that
|
||||||
* provides search engine specific behavior.
|
* provides search engine specific behavior.
|
||||||
@ -88,7 +102,7 @@ abstract class SearchIndex extends ViewableData
|
|||||||
$sources = $this->getClasses();
|
$sources = $this->getClasses();
|
||||||
|
|
||||||
foreach ($sources as $source => $options) {
|
foreach ($sources as $source => $options) {
|
||||||
$sources[$source]['base'] = ClassInfo::baseDataClass($source);
|
$sources[$source]['base'] = DataObject::getSchema()->baseDataClass($source);
|
||||||
$sources[$source]['lookup_chain'] = array();
|
$sources[$source]['lookup_chain'] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,11 +122,13 @@ abstract class SearchIndex extends ViewableData
|
|||||||
$class = null;
|
$class = null;
|
||||||
$options = $baseOptions;
|
$options = $baseOptions;
|
||||||
$singleton = singleton($dataclass);
|
$singleton = singleton($dataclass);
|
||||||
|
$schema = DataObject::getSchema();
|
||||||
|
$className = $singleton->getClassName();
|
||||||
|
|
||||||
if ($hasOne = $singleton->has_one($lookup)) {
|
if ($hasOne = $schema->hasOneComponent($className, $lookup)) {
|
||||||
// we only want to include base class for relation, omit classes that inherited the relation
|
// 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 = Config::inst()->get($dataclass, 'has_one', Config::UNINHERITED);
|
||||||
$relationList = (!is_null($relationList)) ? $relationList : array();
|
$relationList = (!is_null($relationList)) ? $relationList : [];
|
||||||
if (!array_key_exists($lookup, $relationList)) {
|
if (!array_key_exists($lookup, $relationList)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -122,10 +138,10 @@ abstract class SearchIndex extends ViewableData
|
|||||||
'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 = $singleton->has_many($lookup)) {
|
} elseif ($hasMany = $schema->hasManyComponent($className, $lookup)) {
|
||||||
// we only want to include base class for relation, omit classes that inherited the relation
|
// 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 = Config::inst()->get($dataclass, 'has_many', Config::UNINHERITED);
|
||||||
$relationList = (!is_null($relationList)) ? $relationList : array();
|
$relationList = (!is_null($relationList)) ? $relationList : [];
|
||||||
if (!array_key_exists($lookup, $relationList)) {
|
if (!array_key_exists($lookup, $relationList)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -134,25 +150,29 @@ abstract class SearchIndex extends ViewableData
|
|||||||
$options['multi_valued'] = true;
|
$options['multi_valued'] = true;
|
||||||
$options['lookup_chain'][] = array(
|
$options['lookup_chain'][] = array(
|
||||||
'call' => 'method', 'method' => $lookup,
|
'call' => 'method', 'method' => $lookup,
|
||||||
'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $singleton->getRemoteJoinField($lookup, 'has_many')
|
'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $schema->getRemoteJoinField($className, $lookup, 'has_many')
|
||||||
);
|
);
|
||||||
} elseif ($manyMany = $singleton->many_many($lookup)) {
|
} elseif ($manyMany = $schema->manyManyComponent($className, $lookup)) {
|
||||||
// we only want to include base class for relation, omit classes that inherited the relation
|
// 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 = Config::inst()->get($dataclass, 'many_many', Config::UNINHERITED);
|
||||||
$relationList = (!is_null($relationList)) ? $relationList : array();
|
$relationList = (!is_null($relationList)) ? $relationList : [];
|
||||||
if (!array_key_exists($lookup, $relationList)) {
|
if (!array_key_exists($lookup, $relationList)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = $manyMany[1];
|
$class = $manyMany['childClass'];
|
||||||
$options['multi_valued'] = true;
|
$options['multi_valued'] = true;
|
||||||
$options['lookup_chain'][] = array(
|
$options['lookup_chain'][] = array(
|
||||||
'call' => 'method', 'method' => $lookup,
|
'call' => 'method',
|
||||||
'through' => 'many_many', 'class' => $dataclass, 'otherclass' => $class, 'details' => $manyMany
|
'method' => $lookup,
|
||||||
|
'through' => 'many_many',
|
||||||
|
'class' => $dataclass,
|
||||||
|
'otherclass' => $class,
|
||||||
|
'details' => $manyMany,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($class) {
|
if (is_string($class) && $class) {
|
||||||
if (!isset($options['origin'])) {
|
if (!isset($options['origin'])) {
|
||||||
$options['origin'] = $dataclass;
|
$options['origin'] = $dataclass;
|
||||||
}
|
}
|
||||||
@ -180,7 +200,7 @@ abstract class SearchIndex extends ViewableData
|
|||||||
$type = null;
|
$type = null;
|
||||||
$fieldoptions = $options;
|
$fieldoptions = $options;
|
||||||
|
|
||||||
$fields = DataObject::database_fields($dataclass);
|
$fields = DataObject::getSchema()->databaseFields($class);
|
||||||
|
|
||||||
if (isset($fields[$field])) {
|
if (isset($fields[$field])) {
|
||||||
$type = $fields[$field];
|
$type = $fields[$field];
|
||||||
@ -259,8 +279,8 @@ abstract class SearchIndex extends ViewableData
|
|||||||
throw new Exception('Can\'t add class to Index after fields have already been added');
|
throw new Exception('Can\'t add class to Index after fields have already been added');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DataObject::has_own_table($class)) {
|
if (!DataObject::getSchema()->classHasTable($class)) {
|
||||||
throw new InvalidArgumentException('Can\'t add classes which don\'t have data tables (no $db or $has_one set on the class)');
|
throw new \InvalidArgumentException('Can\'t add classes which don\'t have data tables (no $db or $has_one set on the class)');
|
||||||
}
|
}
|
||||||
|
|
||||||
$options = array_merge(array(
|
$options = array_merge(array(
|
||||||
@ -336,14 +356,17 @@ abstract class SearchIndex extends ViewableData
|
|||||||
{
|
{
|
||||||
foreach ($this->getClasses() as $class => $options) {
|
foreach ($this->getClasses() as $class => $options) {
|
||||||
foreach (SearchIntrospection::hierarchy($class, $includeSubclasses, true) as $dataclass) {
|
foreach (SearchIntrospection::hierarchy($class, $includeSubclasses, true) as $dataclass) {
|
||||||
$fields = DataObject::database_fields($dataclass);
|
$fields = DataObject::getSchema()->databaseFields($class);
|
||||||
|
|
||||||
foreach ($fields as $field => $type) {
|
foreach ($fields as $field => $type) {
|
||||||
if (preg_match('/^(\w+)\(/', $type, $match)) {
|
if (preg_match('/^(\w+)\(/', $type, $match)) {
|
||||||
$type = $match[1];
|
$type = $match[1];
|
||||||
}
|
}
|
||||||
list($type, $args) = Object::parse_class_spec($type);
|
list($type, $args) = ClassInfo::parse_class_spec($type);
|
||||||
if (is_subclass_of($type, 'StringField')) {
|
|
||||||
|
// Get class from shortName
|
||||||
|
$object = Injector::inst()->get($type, false, ['Name' => 'test']);
|
||||||
|
|
||||||
|
if (is_subclass_of(get_class($object), 'SilverStripe\ORM\FieldType\DBString')) {
|
||||||
$this->addFulltextField($field);
|
$this->addFulltextField($field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -476,7 +499,10 @@ abstract class SearchIndex extends ViewableData
|
|||||||
*/
|
*/
|
||||||
protected function _getFieldValue($object, $field)
|
protected function _getFieldValue($object, $field)
|
||||||
{
|
{
|
||||||
set_error_handler(create_function('$no, $str', 'throw new Exception("HTML Parse Error: ".$str);'), E_ALL);
|
$errorHandler = function ($no, $str) {
|
||||||
|
throw new Exception('HTML Parse Error: ' . $str);
|
||||||
|
};
|
||||||
|
set_error_handler($errorHandler, E_ALL);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
foreach ($field['lookup_chain'] as $step) {
|
foreach ($field['lookup_chain'] as $step) {
|
||||||
@ -508,9 +534,8 @@ abstract class SearchIndex extends ViewableData
|
|||||||
}
|
}
|
||||||
|
|
||||||
$object = $next;
|
$object = $next;
|
||||||
}
|
} else {
|
||||||
// Otherwise, just call
|
// Otherwise, just call
|
||||||
else {
|
|
||||||
if ($step['call'] == 'method') {
|
if ($step['call'] == 'method') {
|
||||||
$method = $step['method'];
|
$method = $step['method'];
|
||||||
$object = $object->$method();
|
$object = $object->$method();
|
||||||
@ -540,7 +565,8 @@ abstract class SearchIndex extends ViewableData
|
|||||||
* @param Exception $e
|
* @param Exception $e
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static function warn($e) {
|
public static function warn($e)
|
||||||
|
{
|
||||||
// Noisy errors during testing
|
// Noisy errors during testing
|
||||||
if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
|
if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
|
||||||
throw $e;
|
throw $e;
|
||||||
@ -567,7 +593,7 @@ abstract class SearchIndex extends ViewableData
|
|||||||
// First, if this object is directly contained in the index, add it
|
// First, if this object is directly contained in the index, add it
|
||||||
foreach ($this->classes as $searchclass => $options) {
|
foreach ($this->classes as $searchclass => $options) {
|
||||||
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
||||||
$base = ClassInfo::baseDataClass($searchclass);
|
$base = DataObject::getSchema()->baseDataClass($searchclass);
|
||||||
$dirty[$base] = array();
|
$dirty[$base] = array();
|
||||||
foreach ($statefulids as $statefulid) {
|
foreach ($statefulids as $statefulid) {
|
||||||
$key = serialize($statefulid);
|
$key = serialize($statefulid);
|
||||||
@ -595,14 +621,20 @@ abstract class SearchIndex extends ViewableData
|
|||||||
$ids = array($id);
|
$ids = array($id);
|
||||||
|
|
||||||
foreach ($derivation['chain'] as $step) {
|
foreach ($derivation['chain'] as $step) {
|
||||||
|
// Use TableName for queries
|
||||||
|
$tableName = DataObject::getSchema()->tableName($step['class']);
|
||||||
|
|
||||||
if ($step['through'] == 'has_one') {
|
if ($step['through'] == 'has_one') {
|
||||||
$sql = new SQLQuery('"ID"', '"'.$step['class'].'"', '"'.$step['foreignkey'].'" IN ('.implode(',', $ids).')');
|
$sql = new SQLSelect('"ID"', '"'.$tableName.'"', '"'.$step['foreignkey'].'" IN ('.implode(',', $ids).')');
|
||||||
singleton($step['class'])->extend('augmentSQL', $sql);
|
singleton($step['class'])->extend('augmentSQL', $sql);
|
||||||
|
|
||||||
$ids = $sql->execute()->column();
|
$ids = $sql->execute()->column();
|
||||||
} elseif ($step['through'] == 'has_many') {
|
} elseif ($step['through'] == 'has_many') {
|
||||||
$sql = new SQLQuery('"'.$step['class'].'"."ID"', '"'.$step['class'].'"', '"'.$step['otherclass'].'"."ID" IN ('.implode(',', $ids).')');
|
// Use TableName for queries
|
||||||
$sql->addInnerJoin($step['otherclass'], '"'.$step['class'].'"."ID" = "'.$step['otherclass'].'"."'.$step['foreignkey'].'"');
|
$otherTableName = DataObject::getSchema()->tableName($step['otherclass']);
|
||||||
|
|
||||||
|
$sql = new SQLSelect('"'.$tableName.'"."ID"', '"'.$tableName.'"', '"'.$otherTableName.'"."ID" IN ('.implode(',', $ids).')');
|
||||||
|
$sql->addInnerJoin($otherTableName, '"'.$tableName.'"."ID" = "'.$otherTableName.'"."'.$step['foreignkey'].'"');
|
||||||
singleton($step['class'])->extend('augmentSQL', $sql);
|
singleton($step['class'])->extend('augmentSQL', $sql);
|
||||||
|
|
||||||
$ids = $sql->execute()->column();
|
$ids = $sql->execute()->column();
|
||||||
@ -649,95 +681,3 @@ abstract class SearchIndex extends ViewableData
|
|||||||
*/
|
*/
|
||||||
abstract public function init();
|
abstract public function init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A search index that does nothing. Useful for testing
|
|
||||||
*/
|
|
||||||
abstract class SearchIndex_Null extends SearchIndex
|
|
||||||
{
|
|
||||||
public function add($object)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($base, $id, $state)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function commit()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A search index that just records actions. Useful for testing
|
|
||||||
*/
|
|
||||||
abstract class SearchIndex_Recording extends SearchIndex
|
|
||||||
{
|
|
||||||
public $added = array();
|
|
||||||
public $deleted = array();
|
|
||||||
public $committed = false;
|
|
||||||
|
|
||||||
public function reset()
|
|
||||||
{
|
|
||||||
$this->added = array();
|
|
||||||
$this->deleted = array();
|
|
||||||
$this->committed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function add($object)
|
|
||||||
{
|
|
||||||
$res = array();
|
|
||||||
|
|
||||||
$res['ID'] = $object->ID;
|
|
||||||
|
|
||||||
foreach ($this->getFieldsIterator() as $name => $field) {
|
|
||||||
$val = $this->_getFieldValue($object, $field);
|
|
||||||
$res[$name] = $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->added[] = $res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAdded($fields = array())
|
|
||||||
{
|
|
||||||
$res = array();
|
|
||||||
|
|
||||||
foreach ($this->added as $added) {
|
|
||||||
$filtered = array();
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
if (isset($added[$field])) {
|
|
||||||
$filtered[$field] = $added[$field];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$res[] = $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($base, $id, $state)
|
|
||||||
{
|
|
||||||
$this->deleted[] = array('base' => $base, 'id' => $id, 'state' => $state);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function commit()
|
|
||||||
{
|
|
||||||
$this->committed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIndexName()
|
|
||||||
{
|
|
||||||
return get_class($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIsCommitted()
|
|
||||||
{
|
|
||||||
return $this->committed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getService()
|
|
||||||
{
|
|
||||||
// Causes commits to the service to be redirected back to the same object
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
21
code/search/indexes/SearchIndex_Null.php
Normal file
21
code/search/indexes/SearchIndex_Null.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Indexes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A search index that does nothing. Useful for testing
|
||||||
|
*/
|
||||||
|
abstract class SearchIndex_Null extends SearchIndex
|
||||||
|
{
|
||||||
|
public function add($object)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($base, $id, $state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
79
code/search/indexes/SearchIndex_Recording.php
Normal file
79
code/search/indexes/SearchIndex_Recording.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Indexes;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A search index that just records actions. Useful for testing
|
||||||
|
*/
|
||||||
|
abstract class SearchIndex_Recording extends SearchIndex
|
||||||
|
{
|
||||||
|
public $added = array();
|
||||||
|
public $deleted = array();
|
||||||
|
public $committed = false;
|
||||||
|
|
||||||
|
public function reset()
|
||||||
|
{
|
||||||
|
$this->added = array();
|
||||||
|
$this->deleted = array();
|
||||||
|
$this->committed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add($object)
|
||||||
|
{
|
||||||
|
$res = array();
|
||||||
|
|
||||||
|
$res['ID'] = $object->ID;
|
||||||
|
|
||||||
|
foreach ($this->getFieldsIterator() as $name => $field) {
|
||||||
|
$val = $this->_getFieldValue($object, $field);
|
||||||
|
$res[$name] = $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->added[] = $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdded($fields = array())
|
||||||
|
{
|
||||||
|
$res = array();
|
||||||
|
|
||||||
|
foreach ($this->added as $added) {
|
||||||
|
$filtered = array();
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if (isset($added[$field])) {
|
||||||
|
$filtered[$field] = $added[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$res[] = $filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($base, $id, $state)
|
||||||
|
{
|
||||||
|
$this->deleted[] = array('base' => $base, 'id' => $id, 'state' => $state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit()
|
||||||
|
{
|
||||||
|
$this->committed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIndexName()
|
||||||
|
{
|
||||||
|
return get_class($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsCommitted()
|
||||||
|
{
|
||||||
|
return $this->committed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getService()
|
||||||
|
{
|
||||||
|
// Causes commits to the service to be redirected back to the same object
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides batching of search updates
|
* Provides batching of search updates
|
||||||
*/
|
*/
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
|
use DateTime;
|
||||||
|
use DateInterval;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
|
||||||
class SearchUpdateCommitJobProcessor implements QueuedJob
|
class SearchUpdateCommitJobProcessor implements QueuedJob
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -81,7 +94,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
|
|||||||
public static function queue($dirty = true, $startAfter = null)
|
public static function queue($dirty = true, $startAfter = null)
|
||||||
{
|
{
|
||||||
$commit = Injector::inst()->create(__CLASS__);
|
$commit = Injector::inst()->create(__CLASS__);
|
||||||
$id = singleton('QueuedJobService')->queueJob($commit, $startAfter);
|
$id = singleton(QueuedJobService::class)->queueJob($commit, $startAfter);
|
||||||
|
|
||||||
if ($dirty) {
|
if ($dirty) {
|
||||||
$indexes = FullTextSearch::get_indexes();
|
$indexes = FullTextSearch::get_indexes();
|
||||||
@ -159,7 +172,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
|
|||||||
// This could occur if we completed a searchupdate job in a prior request, as well as in
|
// This could occur if we completed a searchupdate job in a prior request, as well as in
|
||||||
// the current request
|
// the current request
|
||||||
$cooldown = Config::inst()->get(__CLASS__, 'cooldown');
|
$cooldown = Config::inst()->get(__CLASS__, 'cooldown');
|
||||||
$now = new DateTime(SS_Datetime::now()->getValue());
|
$now = new DateTime(DBDatetime::now()->getValue());
|
||||||
$now->add(new DateInterval('PT'.$cooldown.'S'));
|
$now->add(new DateInterval('PT'.$cooldown.'S'));
|
||||||
$runat = $now->Format('Y-m-d H:i:s');
|
$runat = $now->Format('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
class SearchUpdateImmediateProcessor extends SearchUpdateProcessor
|
class SearchUpdateImmediateProcessor extends SearchUpdateProcessor
|
||||||
{
|
{
|
||||||
public function triggerProcessing()
|
public function triggerProcessing()
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SearchUpdateMessageQueueProcessor extends SearchUpdateProcessor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The MessageQueue to use when processing updates
|
|
||||||
* @config
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $reindex_queue = "search_indexing";
|
|
||||||
|
|
||||||
public function triggerProcessing()
|
|
||||||
{
|
|
||||||
MessageQueue::send(
|
|
||||||
Config::inst()->get('SearchMessageQueueUpdater', 'reindex_queue'),
|
|
||||||
new MethodInvocationMessage($this, "process")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
|
||||||
abstract class SearchUpdateProcessor
|
abstract class SearchUpdateProcessor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -33,7 +39,7 @@ abstract class SearchUpdateProcessor
|
|||||||
|
|
||||||
public function addDirtyIDs($class, $statefulids, $index)
|
public function addDirtyIDs($class, $statefulids, $index)
|
||||||
{
|
{
|
||||||
$base = ClassInfo::baseDataClass($class);
|
$base = DataObject::getSchema()->baseDataClass($class);
|
||||||
$forclass = isset($this->dirty[$base]) ? $this->dirty[$base] : array();
|
$forclass = isset($this->dirty[$base]) ? $this->dirty[$base] : array();
|
||||||
|
|
||||||
foreach ($statefulids as $statefulid) {
|
foreach ($statefulids as $statefulid) {
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
|
||||||
class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implements QueuedJob
|
class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implements QueuedJob
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -18,7 +26,7 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
|
|||||||
public function triggerProcessing()
|
public function triggerProcessing()
|
||||||
{
|
{
|
||||||
parent::triggerProcessing();
|
parent::triggerProcessing();
|
||||||
singleton('QueuedJobService')->queueJob($this);
|
singleton(QueuedJobService::class)->queueJob($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTitle()
|
public function getTitle()
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Queries;
|
||||||
|
|
||||||
|
use SilverStripe\View\ViewableData;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a search query
|
* Represents a search query
|
||||||
*
|
*
|
||||||
@ -119,34 +124,3 @@ class SearchQuery extends ViewableData
|
|||||||
return "Search Query\n";
|
return "Search Query\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly
|
|
||||||
* open ended) range
|
|
||||||
*/
|
|
||||||
class SearchQuery_Range
|
|
||||||
{
|
|
||||||
public $start = null;
|
|
||||||
public $end = null;
|
|
||||||
|
|
||||||
public function __construct($start = null, $end = null)
|
|
||||||
{
|
|
||||||
$this->start = $start;
|
|
||||||
$this->end = $end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function start($start)
|
|
||||||
{
|
|
||||||
$this->start = $start;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function end($end)
|
|
||||||
{
|
|
||||||
$this->end = $end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isfiltered()
|
|
||||||
{
|
|
||||||
return $this->start !== null || $this->end !== null;
|
|
||||||
}
|
|
||||||
}
|
|
34
code/search/queries/SearchQuery_Range.php
Normal file
34
code/search/queries/SearchQuery_Range.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Queries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly
|
||||||
|
* open ended) range
|
||||||
|
*/
|
||||||
|
class SearchQuery_Range
|
||||||
|
{
|
||||||
|
public $start = null;
|
||||||
|
public $end = null;
|
||||||
|
|
||||||
|
public function __construct($start = null, $end = null)
|
||||||
|
{
|
||||||
|
$this->start = $start;
|
||||||
|
$this->end = $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function start($start)
|
||||||
|
{
|
||||||
|
$this->start = $start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end($end)
|
||||||
|
{
|
||||||
|
$this->end = $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isfiltered()
|
||||||
|
{
|
||||||
|
return $this->start !== null || $this->end !== null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Updaters;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Configurable;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for capturing changes to DataObjects and triggering index updates of the resulting dirty index
|
* This class is responsible for capturing changes to DataObjects and triggering index updates of the resulting dirty index
|
||||||
* items.
|
* items.
|
||||||
@ -12,8 +25,19 @@
|
|||||||
*
|
*
|
||||||
* TODO: The way we bind in is awful hacky.
|
* TODO: The way we bind in is awful hacky.
|
||||||
*/
|
*/
|
||||||
class SearchUpdater extends Object
|
|
||||||
|
class SearchUpdater
|
||||||
{
|
{
|
||||||
|
use Configurable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to register the shutdown function to flush. Can be disabled for example in unit testing.
|
||||||
|
*
|
||||||
|
* @config
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $flush_on_shutdown = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the database object with a subclass that captures all manipulations and passes them to us
|
* Replace the database object with a subclass that captures all manipulations and passes them to us
|
||||||
*/
|
*/
|
||||||
@ -21,44 +45,30 @@ class SearchUpdater extends Object
|
|||||||
{
|
{
|
||||||
global $databaseConfig;
|
global $databaseConfig;
|
||||||
|
|
||||||
$current = DB::getConn();
|
$current = DB::get_conn();
|
||||||
if (!$current || !$current->currentDatabase() || @$current->isManipulationCapture) {
|
|
||||||
|
if (!$current || !$current->getSelectedDatabase() || @$current->isManipulationCapture) {
|
||||||
return;
|
return;
|
||||||
} // If not yet set, or its already captured, just return
|
} // If not yet set, or its already captured, just return
|
||||||
|
|
||||||
$type = get_class($current);
|
$type = (new ReflectionClass($current))->getShortName();
|
||||||
$file = TEMP_FOLDER."/.cache.SMC.$type";
|
$dbClass = 'SilverStripe\FullTextSearch\Captures\SearchManipulateCapture_' . $type;
|
||||||
|
|
||||||
if (!is_file($file)) {
|
// Check if Capture class exists.
|
||||||
file_put_contents($file, "<?php
|
if (!class_exists($dbClass)) {
|
||||||
class SearchManipulateCapture_$type extends $type {
|
return;
|
||||||
public \$isManipulationCapture = true;
|
|
||||||
|
|
||||||
function manipulate(\$manipulation) {
|
|
||||||
\$res = parent::manipulate(\$manipulation);
|
|
||||||
SearchUpdater::handle_manipulation(\$manipulation);
|
|
||||||
return \$res;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once($file);
|
|
||||||
$dbClass = 'SearchManipulateCapture_'.$type;
|
|
||||||
|
|
||||||
/** @var SS_Database $captured */
|
/** @var SS_Database $captured */
|
||||||
$captured = new $dbClass($databaseConfig);
|
$captured = new $dbClass($databaseConfig);
|
||||||
|
|
||||||
// Framework 3.2+ ORM needs some dependencies set
|
|
||||||
if (method_exists($captured, "setConnector")) {
|
|
||||||
$captured->setConnector($current->getConnector());
|
$captured->setConnector($current->getConnector());
|
||||||
$captured->setQueryBuilder($current->getQueryBuilder());
|
$captured->setQueryBuilder($current->getQueryBuilder());
|
||||||
$captured->setSchemaManager($current->getSchemaManager());
|
$captured->setSchemaManager($current->getSchemaManager());
|
||||||
}
|
|
||||||
|
|
||||||
// The connection might have had it's name changed (like if we're currently in a test)
|
// The connection might have had it's name changed (like if we're currently in a test)
|
||||||
$captured->selectDatabase($current->currentDatabase());
|
$captured->selectDatabase($current->getSelectedDatabase());
|
||||||
DB::setConn($captured);
|
DB::set_conn($captured);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static $registered = false;
|
public static $registered = false;
|
||||||
@ -79,7 +89,9 @@ class SearchUpdater extends Object
|
|||||||
{
|
{
|
||||||
// First, extract any state that is in the manipulation itself
|
// First, extract any state that is in the manipulation itself
|
||||||
foreach ($manipulation as $table => $details) {
|
foreach ($manipulation as $table => $details) {
|
||||||
$manipulation[$table]['class'] = $table;
|
if (!isset($manipulation[$table]['class'])) {
|
||||||
|
$manipulation[$table]['class'] = DataObject::getSchema()->tableClass($table);
|
||||||
|
}
|
||||||
$manipulation[$table]['state'] = array();
|
$manipulation[$table]['state'] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +112,7 @@ class SearchUpdater extends Object
|
|||||||
$command = $details['command'];
|
$command = $details['command'];
|
||||||
$fields = isset($details['fields']) ? $details['fields'] : array();
|
$fields = isset($details['fields']) ? $details['fields'] : array();
|
||||||
|
|
||||||
$base = ClassInfo::baseDataClass($class);
|
$base = DataObject::getSchema()->baseDataClass($class);
|
||||||
$key = "$id:$base:".serialize($state);
|
$key = "$id:$base:".serialize($state);
|
||||||
|
|
||||||
$statefulids = array(array('id' => $id, 'state' => $state));
|
$statefulids = array(array('id' => $id, 'state' => $state));
|
||||||
@ -115,8 +127,7 @@ class SearchUpdater extends Object
|
|||||||
'command' => $command,
|
'command' => $command,
|
||||||
'fields' => array()
|
'fields' => array()
|
||||||
);
|
);
|
||||||
}
|
} // Otherwise update the class label if it's more specific than the currently recorded one
|
||||||
// Otherwise update the class label if it's more specific than the currently recorded one
|
|
||||||
elseif (is_subclass_of($class, $writes[$key]['class'])) {
|
elseif (is_subclass_of($class, $writes[$key]['class'])) {
|
||||||
$writes[$key]['class'] = $class;
|
$writes[$key]['class'] = $class;
|
||||||
}
|
}
|
||||||
@ -162,7 +173,7 @@ class SearchUpdater extends Object
|
|||||||
foreach ($dirtyids as $dirtyclass => $ids) {
|
foreach ($dirtyids as $dirtyclass => $ids) {
|
||||||
if ($ids) {
|
if ($ids) {
|
||||||
if (!self::$processor) {
|
if (!self::$processor) {
|
||||||
self::$processor = Injector::inst()->create('SearchUpdateProcessor');
|
self::$processor = Injector::inst()->create(SearchUpdateImmediateProcessor::class);
|
||||||
}
|
}
|
||||||
self::$processor->addDirtyIDs($dirtyclass, $ids, $index);
|
self::$processor->addDirtyIDs($dirtyclass, $ids, $index);
|
||||||
}
|
}
|
||||||
@ -172,13 +183,8 @@ class SearchUpdater extends Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we do have some work to do register the shutdown function to actually do the work
|
// If we do have some work to do register the shutdown function to actually do the work
|
||||||
|
if (self::$processor && !self::$registered && self::config()->get('flush_on_shutdown')) {
|
||||||
// Don't do it if we're testing - there's no database connection outside the test methods, so we'd
|
register_shutdown_function(array(SearchUpdater::class, "flush_dirty_indexes"));
|
||||||
// just get errors
|
|
||||||
$runningTests = class_exists('SapphireTest', false) && SapphireTest::is_running_test();
|
|
||||||
|
|
||||||
if (self::$processor && !self::$registered && !$runningTests) {
|
|
||||||
register_shutdown_function(array("SearchUpdater", "flush_dirty_indexes"));
|
|
||||||
self::$registered = true;
|
self::$registered = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Updaters;
|
||||||
|
|
||||||
|
use SilverStripe\Control\RequestFilter;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\HTTPResponse;
|
||||||
|
|
||||||
|
class SearchUpdater_BindManipulationCaptureFilter implements RequestFilter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function preRequest(HTTPRequest $request)
|
||||||
|
{
|
||||||
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
||||||
|
{
|
||||||
|
/* NOP */
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Updaters;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataExtension;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete operations do not use database manipulations.
|
* Delete operations do not use database manipulations.
|
||||||
*
|
*
|
||||||
* If a delete has been requested, force a write on objects that should be
|
* If a delete has been requested, force a write on objects that should be
|
||||||
* indexed. This causes the object to be marked for deletion from the index.
|
* indexed. This causes the object to be marked for deletion from the index.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SearchUpdater_ObjectHandler extends DataExtension
|
class SearchUpdater_ObjectHandler extends DataExtension
|
||||||
{
|
{
|
||||||
public function onAfterDelete()
|
public function onAfterDelete()
|
||||||
@ -19,18 +28,23 @@ class SearchUpdater_ObjectHandler extends DataExtension
|
|||||||
// Note: Some extensions require entire hierarchy passed to augmentWrite()
|
// Note: Some extensions require entire hierarchy passed to augmentWrite()
|
||||||
$manipulation = array();
|
$manipulation = array();
|
||||||
foreach (ClassInfo::ancestry($this->owner) as $class) {
|
foreach (ClassInfo::ancestry($this->owner) as $class) {
|
||||||
if (!is_subclass_of($class, 'DataObject')) {
|
if (!is_subclass_of($class, DataObject::class)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$manipulation[$class] = array(
|
|
||||||
|
$tableName = DataObject::getSchema()->tableName($class);
|
||||||
|
$manipulation[$tableName] = array(
|
||||||
'fields' => array(),
|
'fields' => array(),
|
||||||
'id' => $this->owner->ID,
|
'id' => $this->owner->ID,
|
||||||
|
'class' => $class,
|
||||||
// Note: 'delete' command not actually handled by manipulations,
|
// Note: 'delete' command not actually handled by manipulations,
|
||||||
// but added so that SearchUpdater can detect the deletion
|
// but added so that SearchUpdater can detect the deletion
|
||||||
'command' => 'delete'
|
'command' => 'delete'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->owner->extend('augmentWrite', $manipulation);
|
$this->owner->extend('augmentWrite', $manipulation);
|
||||||
|
|
||||||
SearchUpdater::handle_manipulation($manipulation);
|
SearchUpdater::handle_manipulation($manipulation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,15 +60,13 @@ class SearchUpdater_ObjectHandler extends DataExtension
|
|||||||
$id = $this->owner->ID;
|
$id = $this->owner->ID;
|
||||||
$class = $this->owner->ClassName;
|
$class = $this->owner->ClassName;
|
||||||
$state = SearchVariant::current_state($class);
|
$state = SearchVariant::current_state($class);
|
||||||
$base = ClassInfo::baseDataClass($class);
|
$base = DataObject::getSchema()->baseDataClass($class);
|
||||||
$key = "$id:$base:".serialize($state);
|
$key = "$id:$base:".serialize($state);
|
||||||
|
|
||||||
$statefulids = array(
|
$statefulids = array(array(
|
||||||
array(
|
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'state' => $state
|
'state' => $state
|
||||||
)
|
));
|
||||||
);
|
|
||||||
|
|
||||||
$writes = array(
|
$writes = array(
|
||||||
$key => array(
|
$key => array(
|
@ -1,5 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Variants;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\FullTextSearch\Utils\CombinationsArrayIterator;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Search Variant handles decorators and other situations where the items to reindex or search through are modified
|
* A Search Variant handles decorators and other situations where the items to reindex or search through are modified
|
||||||
* from the default state - for instance, dealing with Versioned or Subsite
|
* from the default state - for instance, dealing with Versioned or Subsite
|
||||||
@ -21,7 +28,10 @@ abstract class SearchVariant
|
|||||||
* Return false if there is something missing from the environment (probably a
|
* Return false if there is something missing from the environment (probably a
|
||||||
* not installed module) that means this variant can't apply to any class
|
* not installed module) that means this variant can't apply to any class
|
||||||
*/
|
*/
|
||||||
abstract public function appliesToEnvironment();
|
public function appliesToEnvironment()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if this variant applies to the passed class & subclass
|
* Return true if this variant applies to the passed class & subclass
|
||||||
@ -73,7 +83,7 @@ abstract class SearchVariant
|
|||||||
{
|
{
|
||||||
if (!$class) {
|
if (!$class) {
|
||||||
if (self::$variants === null) {
|
if (self::$variants === null) {
|
||||||
$classes = ClassInfo::subclassesFor('SearchVariant');
|
$classes = ClassInfo::subclassesFor(static::class);
|
||||||
|
|
||||||
$concrete = array();
|
$concrete = array();
|
||||||
foreach ($classes as $variantclass) {
|
foreach ($classes as $variantclass) {
|
||||||
@ -209,7 +219,8 @@ abstract class SearchVariant
|
|||||||
* @param string $name Field name
|
* @param string $name Field name
|
||||||
* @param array $field Field spec
|
* @param array $field Field spec
|
||||||
*/
|
*/
|
||||||
protected function addFilterField($index, $name, $field) {
|
protected function addFilterField($index, $name, $field)
|
||||||
|
{
|
||||||
// If field already exists, make sure to merge origin / base fields
|
// If field already exists, make sure to merge origin / base fields
|
||||||
if (isset($index->filterFields[$name])) {
|
if (isset($index->filterFields[$name])) {
|
||||||
$field['base'] = $this->mergeClasses(
|
$field['base'] = $this->mergeClasses(
|
||||||
@ -234,7 +245,8 @@ abstract class SearchVariant
|
|||||||
* @param array|string $right Right class(es)
|
* @param array|string $right Right class(es)
|
||||||
* @return array|string List of classes, or single class
|
* @return array|string List of classes, or single class
|
||||||
*/
|
*/
|
||||||
protected function mergeClasses($left, $right) {
|
protected function mergeClasses($left, $right)
|
||||||
|
{
|
||||||
// Merge together and remove dupes
|
// Merge together and remove dupes
|
||||||
if (!is_array($left)) {
|
if (!is_array($left)) {
|
||||||
$left = array($left);
|
$left = array($left);
|
||||||
@ -251,32 +263,3 @@ abstract class SearchVariant
|
|||||||
return $merged;
|
return $merged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal utility class used to hold the state of the SearchVariant::with call
|
|
||||||
*/
|
|
||||||
class SearchVariant_Caller
|
|
||||||
{
|
|
||||||
protected $variants = null;
|
|
||||||
|
|
||||||
public function __construct($variants)
|
|
||||||
{
|
|
||||||
$this->variants = $variants;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null)
|
|
||||||
{
|
|
||||||
$values = array();
|
|
||||||
|
|
||||||
foreach ($this->variants as $variant) {
|
|
||||||
if (method_exists($variant, $method)) {
|
|
||||||
$value = $variant->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
|
||||||
if ($value !== null) {
|
|
||||||
$values[] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $values;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Variants;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\Queries\SQLSelect;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
|
||||||
|
if (!class_exists('Subsite') || !class_exists('SubsitePolyhome')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
|
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
|
||||||
{
|
{
|
||||||
@ -48,7 +56,7 @@ class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
|
|||||||
'name' => '_subsite',
|
'name' => '_subsite',
|
||||||
'field' => '_subsite',
|
'field' => '_subsite',
|
||||||
'fullfield' => '_subsite',
|
'fullfield' => '_subsite',
|
||||||
'base' => ClassInfo::baseDataClass($class),
|
'base' => DataObject::getSchema()->baseDataClass($class),
|
||||||
'origin' => $class,
|
'origin' => $class,
|
||||||
'type' => 'Int',
|
'type' => 'Int',
|
||||||
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
||||||
@ -77,7 +85,7 @@ class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self::$subsites === null) {
|
if (self::$subsites === null) {
|
||||||
$query = new SQLQuery('ID', 'Subsite');
|
$query = new SQLSelect('ID', 'Subsite');
|
||||||
self::$subsites = array_merge(array('0'), $query->execute()->column());
|
self::$subsites = array_merge(array('0'), $query->execute()->column());
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Variants;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\Queries\SQLSelect;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Security\Permission;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
|
|
||||||
|
if (!class_exists('Subsite')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
class SearchVariantSubsites extends SearchVariant
|
class SearchVariantSubsites extends SearchVariant
|
||||||
{
|
{
|
||||||
public function appliesToEnvironment()
|
public function appliesToEnvironment()
|
||||||
@ -53,7 +65,7 @@ class SearchVariantSubsites extends SearchVariant
|
|||||||
'name' => '_subsite',
|
'name' => '_subsite',
|
||||||
'field' => '_subsite',
|
'field' => '_subsite',
|
||||||
'fullfield' => '_subsite',
|
'fullfield' => '_subsite',
|
||||||
'base' => ClassInfo::baseDataClass($class),
|
'base' => DataObject::getSchema()->baseDataClass($class),
|
||||||
'origin' => $class,
|
'origin' => $class,
|
||||||
'type' => 'Int',
|
'type' => 'Int',
|
||||||
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
||||||
@ -86,7 +98,7 @@ class SearchVariantSubsites extends SearchVariant
|
|||||||
public function extractManipulationWriteState(&$writes)
|
public function extractManipulationWriteState(&$writes)
|
||||||
{
|
{
|
||||||
$self = get_class($this);
|
$self = get_class($this);
|
||||||
$query = new SQLQuery('"ID"', '"Subsite"');
|
$query = new SQLSelect('"ID"', '"Subsite"');
|
||||||
$subsites = array_merge(array('0'), $query->execute()->column());
|
$subsites = array_merge(array('0'), $query->execute()->column());
|
||||||
|
|
||||||
foreach ($writes as $key => $write) {
|
foreach ($writes as $key => $write) {
|
||||||
@ -97,12 +109,10 @@ class SearchVariantSubsites extends SearchVariant
|
|||||||
|
|
||||||
if (isset($write['fields']['SiteTree:SubsiteID'])) {
|
if (isset($write['fields']['SiteTree:SubsiteID'])) {
|
||||||
$subsitesForWrite = array($write['fields']['SiteTree:SubsiteID']);
|
$subsitesForWrite = array($write['fields']['SiteTree:SubsiteID']);
|
||||||
}
|
} // files in subsite 0 should be in all subsites as they are global
|
||||||
// files in subsite 0 should be in all subsites as they are global
|
|
||||||
elseif (isset($write['fields']['File:SubsiteID']) && intval($write['fields']['File:SubsiteID']) !== 0) {
|
elseif (isset($write['fields']['File:SubsiteID']) && intval($write['fields']['File:SubsiteID']) !== 0) {
|
||||||
$subsitesForWrite = array($write['fields']['File:SubsiteID']);
|
$subsitesForWrite = array($write['fields']['File:SubsiteID']);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$subsitesForWrite = $subsites;
|
$subsitesForWrite = $subsites;
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Variants;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
|
|
||||||
class SearchVariantVersioned extends SearchVariant
|
class SearchVariantVersioned extends SearchVariant
|
||||||
{
|
{
|
||||||
public function appliesToEnvironment()
|
|
||||||
{
|
|
||||||
return class_exists('Versioned');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function appliesTo($class, $includeSubclasses)
|
public function appliesTo($class, $includeSubclasses)
|
||||||
{
|
{
|
||||||
return SearchIntrospection::has_extension($class, 'Versioned', $includeSubclasses);
|
return SearchIntrospection::has_extension($class, Versioned::class, $includeSubclasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function currentState()
|
public function currentState()
|
||||||
{
|
{
|
||||||
return Versioned::current_stage();
|
return Versioned::get_stage();
|
||||||
}
|
}
|
||||||
public function reindexStates()
|
public function reindexStates()
|
||||||
{
|
{
|
||||||
@ -22,7 +25,7 @@ class SearchVariantVersioned extends SearchVariant
|
|||||||
}
|
}
|
||||||
public function activateState($state)
|
public function activateState($state)
|
||||||
{
|
{
|
||||||
Versioned::reading_stage($state);
|
Versioned::set_stage($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function alterDefinition($class, $index)
|
public function alterDefinition($class, $index)
|
||||||
@ -33,7 +36,7 @@ class SearchVariantVersioned extends SearchVariant
|
|||||||
'name' => '_versionedstage',
|
'name' => '_versionedstage',
|
||||||
'field' => '_versionedstage',
|
'field' => '_versionedstage',
|
||||||
'fullfield' => '_versionedstage',
|
'fullfield' => '_versionedstage',
|
||||||
'base' => ClassInfo::baseDataClass($class),
|
'base' => DataObject::getSchema()->baseDataClass($class),
|
||||||
'origin' => $class,
|
'origin' => $class,
|
||||||
'type' => 'String',
|
'type' => 'String',
|
||||||
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
||||||
@ -42,7 +45,7 @@ class SearchVariantVersioned extends SearchVariant
|
|||||||
|
|
||||||
public function alterQuery($query, $index)
|
public function alterQuery($query, $index)
|
||||||
{
|
{
|
||||||
$stage = Versioned::current_stage();
|
$stage = $this->currentState();
|
||||||
$query->filter('_versionedstage', array($stage, SearchQuery::$missing));
|
$query->filter('_versionedstage', array($stage, SearchQuery::$missing));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ class SearchVariantVersioned extends SearchVariant
|
|||||||
$stage = 'Stage';
|
$stage = 'Stage';
|
||||||
|
|
||||||
if (preg_match('/^(.*)_Live$/', $table, $matches)) {
|
if (preg_match('/^(.*)_Live$/', $table, $matches)) {
|
||||||
$class = $matches[1];
|
$class = DataObject::getSchema()->tableClass($matches[1]);
|
||||||
$stage = 'Live';
|
$stage = 'Live';
|
||||||
}
|
}
|
||||||
|
|
32
code/search/variants/SearchVariant_Caller.php
Normal file
32
code/search/variants/SearchVariant_Caller.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Variants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal utility class used to hold the state of the SearchVariant::with call
|
||||||
|
*/
|
||||||
|
class SearchVariant_Caller
|
||||||
|
{
|
||||||
|
protected $variants = null;
|
||||||
|
|
||||||
|
public function __construct($variants)
|
||||||
|
{
|
||||||
|
$this->variants = $variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function call($method, &$a1 = null, &$a2 = null, &$a3 = null, &$a4 = null, &$a5 = null, &$a6 = null, &$a7 = null)
|
||||||
|
{
|
||||||
|
$values = array();
|
||||||
|
|
||||||
|
foreach ($this->variants as $variant) {
|
||||||
|
if (method_exists($variant, $method)) {
|
||||||
|
$value = $variant->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||||
|
if ($value !== null) {
|
||||||
|
$values[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Monolog\Formatter\LineFormatter;
|
namespace SilverStripe\FullTextSearch\Solr;
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Logger;
|
use SilverStripe\Control\Director;
|
||||||
use Psr\Log\LoggerInterface;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
|
||||||
|
|
||||||
class Solr
|
class Solr
|
||||||
{
|
{
|
||||||
@ -79,13 +81,13 @@ class Solr
|
|||||||
|
|
||||||
if (version_compare($version, '4', '>=')) {
|
if (version_compare($version, '4', '>=')) {
|
||||||
$versionDefaults = array(
|
$versionDefaults = array(
|
||||||
'service' => 'Solr4Service',
|
'service' => Solr4Service::class,
|
||||||
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/extras/',
|
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/extras/',
|
||||||
'templatespath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/templates/',
|
'templatespath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/templates/',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$versionDefaults = array(
|
$versionDefaults = array(
|
||||||
'service' => 'Solr3Service',
|
'service' => Solr3Service::class,
|
||||||
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/3/extras/',
|
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/3/extras/',
|
||||||
'templatespath' => Director::baseFolder().'/fulltextsearch/conf/solr/3/templates/',
|
'templatespath' => Director::baseFolder().'/fulltextsearch/conf/solr/3/templates/',
|
||||||
);
|
);
|
||||||
@ -118,7 +120,10 @@ class Solr
|
|||||||
|
|
||||||
if (!self::$service_singleton) {
|
if (!self::$service_singleton) {
|
||||||
self::$service_singleton = Object::create(
|
self::$service_singleton = Object::create(
|
||||||
$options['service'], $options['host'], $options['port'], $options['path']
|
$options['service'],
|
||||||
|
$options['host'],
|
||||||
|
$options['port'],
|
||||||
|
$options['path']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +142,7 @@ class Solr
|
|||||||
|
|
||||||
public static function get_indexes()
|
public static function get_indexes()
|
||||||
{
|
{
|
||||||
return FullTextSearch::get_indexes('SolrIndex');
|
return FullTextSearch::get_indexes(SolrIndex::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,299 +154,12 @@ class Solr
|
|||||||
static $included = false;
|
static $included = false;
|
||||||
|
|
||||||
if (!$included) {
|
if (!$included) {
|
||||||
set_include_path(get_include_path() . PATH_SEPARATOR . Director::baseFolder() . '/fulltextsearch/thirdparty/solr-php-client');
|
$solr_php_path = __DIR__. '/../..' . '/thirdparty/solr-php-client/';
|
||||||
require_once('Apache/Solr/Service.php');
|
set_include_path(get_include_path() . PATH_SEPARATOR . $solr_php_path);
|
||||||
require_once('Apache/Solr/Document.php');
|
require_once($solr_php_path . 'Apache/Solr/Service.php');
|
||||||
|
require_once($solr_php_path . 'Apache/Solr/Document.php');
|
||||||
|
|
||||||
$included = true;
|
$included = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class for build tasks
|
|
||||||
*/
|
|
||||||
class Solr_BuildTask extends BuildTask
|
|
||||||
{
|
|
||||||
protected $enabled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger
|
|
||||||
*
|
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
|
||||||
protected $logger = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current logger
|
|
||||||
*
|
|
||||||
* @return LoggerInterface
|
|
||||||
*/
|
|
||||||
public function getLogger()
|
|
||||||
{
|
|
||||||
return $this->logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a new logger
|
|
||||||
*
|
|
||||||
* @param LoggerInterface $logger
|
|
||||||
*/
|
|
||||||
public function setLogger(LoggerInterface $logger)
|
|
||||||
{
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return SearchLogFactory
|
|
||||||
*/
|
|
||||||
protected function getLoggerFactory()
|
|
||||||
{
|
|
||||||
return Injector::inst()->get('SearchLogFactory');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup task
|
|
||||||
*
|
|
||||||
* @param SS_HTTPReqest $request
|
|
||||||
*/
|
|
||||||
public function run($request)
|
|
||||||
{
|
|
||||||
$name = get_class($this);
|
|
||||||
$verbose = $request->getVar('verbose');
|
|
||||||
|
|
||||||
// Set new logger
|
|
||||||
$logger = $this
|
|
||||||
->getLoggerFactory()
|
|
||||||
->getOutputLogger($name, $verbose);
|
|
||||||
$this->setLogger($logger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Solr_Configure extends Solr_BuildTask
|
|
||||||
{
|
|
||||||
protected $enabled = true;
|
|
||||||
|
|
||||||
public function run($request)
|
|
||||||
{
|
|
||||||
parent::run($request);
|
|
||||||
|
|
||||||
// Find the IndexStore handler, which will handle uploading config files to Solr
|
|
||||||
$store = $this->getSolrConfigStore();
|
|
||||||
$indexes = Solr::get_indexes();
|
|
||||||
foreach ($indexes as $instance) {
|
|
||||||
try {
|
|
||||||
$this->updateIndex($instance, $store);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// We got an exception. Warn, but continue to next index.
|
|
||||||
$this
|
|
||||||
->getLogger()
|
|
||||||
->error("Failure: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($e)) {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the index on the given store
|
|
||||||
*
|
|
||||||
* @param SolrIndex $instance Instance
|
|
||||||
* @param SolrConfigStore $store
|
|
||||||
*/
|
|
||||||
protected function updateIndex($instance, $store)
|
|
||||||
{
|
|
||||||
$index = $instance->getIndexName();
|
|
||||||
$this->getLogger()->info("Configuring $index.");
|
|
||||||
|
|
||||||
// Upload the config files for this index
|
|
||||||
$this->getLogger()->info("Uploading configuration ...");
|
|
||||||
$instance->uploadConfig($store);
|
|
||||||
|
|
||||||
// Then tell Solr to use those config files
|
|
||||||
$service = Solr::service();
|
|
||||||
if ($service->coreIsActive($index)) {
|
|
||||||
$this->getLogger()->info("Reloading core ...");
|
|
||||||
$service->coreReload($index);
|
|
||||||
} else {
|
|
||||||
$this->getLogger()->info("Creating core ...");
|
|
||||||
$service->coreCreate($index, $store->instanceDir($index));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getLogger()->info("Done");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get config store
|
|
||||||
*
|
|
||||||
* @return SolrConfigStore
|
|
||||||
*/
|
|
||||||
protected function getSolrConfigStore()
|
|
||||||
{
|
|
||||||
$options = Solr::solr_options();
|
|
||||||
|
|
||||||
if (!isset($options['indexstore']) || !($indexstore = $options['indexstore'])) {
|
|
||||||
user_error('No index configuration for Solr provided', E_USER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the IndexStore handler, which will handle uploading config files to Solr
|
|
||||||
$mode = $indexstore['mode'];
|
|
||||||
|
|
||||||
if ($mode == 'file') {
|
|
||||||
return new SolrConfigStore_File($indexstore);
|
|
||||||
} elseif ($mode == 'webdav') {
|
|
||||||
return new SolrConfigStore_WebDAV($indexstore);
|
|
||||||
} elseif (ClassInfo::exists($mode) && ClassInfo::classImplements($mode, 'SolrConfigStore')) {
|
|
||||||
return new $mode($indexstore);
|
|
||||||
} else {
|
|
||||||
user_error('Unknown Solr index mode '.$indexstore['mode'], E_USER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task used for both initiating a new reindex, as well as for processing incremental batches
|
|
||||||
* within a reindex.
|
|
||||||
*
|
|
||||||
* When running a complete reindex you can provide any of the following
|
|
||||||
* - class (to limit to a single class)
|
|
||||||
* - verbose (optional)
|
|
||||||
*
|
|
||||||
* When running with a single batch, provide the following querystring arguments:
|
|
||||||
* - start
|
|
||||||
* - index
|
|
||||||
* - class
|
|
||||||
* - variantstate
|
|
||||||
* - verbose (optional)
|
|
||||||
*/
|
|
||||||
class Solr_Reindex extends Solr_BuildTask
|
|
||||||
{
|
|
||||||
protected $enabled = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of records to load and index per request
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
* @config
|
|
||||||
*/
|
|
||||||
private static $recordsPerRequest = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the reindex handler
|
|
||||||
*
|
|
||||||
* @return SolrReindexHandler
|
|
||||||
*/
|
|
||||||
protected function getHandler()
|
|
||||||
{
|
|
||||||
return Injector::inst()->get('SolrReindexHandler');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SS_HTTPRequest $request
|
|
||||||
*/
|
|
||||||
public function run($request)
|
|
||||||
{
|
|
||||||
parent::run($request);
|
|
||||||
|
|
||||||
// Reset state
|
|
||||||
$originalState = SearchVariant::current_state();
|
|
||||||
$this->doReindex($request);
|
|
||||||
SearchVariant::activate_state($originalState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SS_HTTPRequest $request
|
|
||||||
*/
|
|
||||||
protected function doReindex($request)
|
|
||||||
{
|
|
||||||
$class = $request->getVar('class');
|
|
||||||
|
|
||||||
$index = $request->getVar('index');
|
|
||||||
|
|
||||||
//find the index classname by IndexName
|
|
||||||
// this is for when index names do not match the class name (this can be done by overloading getIndexName() on
|
|
||||||
// indexes
|
|
||||||
if ($index && !ClassInfo::exists($index)) {
|
|
||||||
foreach(ClassInfo::subclassesFor('SolrIndex') as $solrIndexClass) {
|
|
||||||
$reflection = new ReflectionClass($solrIndexClass);
|
|
||||||
//skip over abstract classes
|
|
||||||
if (!$reflection->isInstantiable()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//check the indexname matches the index passed to the request
|
|
||||||
if (!strcasecmp(singleton($solrIndexClass)->getIndexName(), $index)) {
|
|
||||||
//if we match, set the correct index name and move on
|
|
||||||
$index = $solrIndexClass;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated reindex mechanism
|
|
||||||
$start = $request->getVar('start');
|
|
||||||
if ($start !== null) {
|
|
||||||
// Run single batch directly
|
|
||||||
$indexInstance = singleton($index);
|
|
||||||
$state = json_decode($request->getVar('variantstate'), true);
|
|
||||||
$this->runFrom($indexInstance, $class, $start, $state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are re-indexing a single group
|
|
||||||
// If not using queuedjobs, we need to invoke Solr_Reindex as a separate process
|
|
||||||
// Otherwise each group is processed via a SolrReindexGroupJob
|
|
||||||
$groups = $request->getVar('groups');
|
|
||||||
$handler = $this->getHandler();
|
|
||||||
if ($groups) {
|
|
||||||
// Run grouped batches (id % groups = group)
|
|
||||||
$group = $request->getVar('group');
|
|
||||||
$indexInstance = singleton($index);
|
|
||||||
$state = json_decode($request->getVar('variantstate'), true);
|
|
||||||
|
|
||||||
$handler->runGroup($this->getLogger(), $indexInstance, $state, $class, $groups, $group);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If run at the top level, delegate to appropriate handler
|
|
||||||
$self = get_class($this);
|
|
||||||
$handler->triggerReindex($this->getLogger(), $this->config()->recordsPerRequest, $self, $class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since version 2.0.0
|
|
||||||
*/
|
|
||||||
protected function runFrom($index, $class, $start, $variantstate)
|
|
||||||
{
|
|
||||||
DeprecationTest_Deprecation::notice('2.0.0', 'Solr_Reindex now uses a new grouping mechanism');
|
|
||||||
|
|
||||||
// Set time limit and state
|
|
||||||
increase_time_limit_to();
|
|
||||||
SearchVariant::activate_state($variantstate);
|
|
||||||
|
|
||||||
// Generate filtered list
|
|
||||||
$items = DataList::create($class)
|
|
||||||
->limit($this->config()->recordsPerRequest, $start);
|
|
||||||
|
|
||||||
// Add child filter
|
|
||||||
$classes = $index->getClasses();
|
|
||||||
$options = $classes[$class];
|
|
||||||
if (!$options['include_children']) {
|
|
||||||
$items = $items->filter('ClassName', $class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process selected records in this class
|
|
||||||
$this->getLogger()->info("Adding $class");
|
|
||||||
foreach ($items->sort("ID") as $item) {
|
|
||||||
$this->getLogger()->debug($item->ID);
|
|
||||||
|
|
||||||
// See SearchUpdater_ObjectHandler::triggerReindex
|
|
||||||
$item->triggerReindex();
|
|
||||||
$item->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getLogger()->info("Done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Solr3Service_Core extends SolrService_Core
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
class Solr3Service extends SolrService
|
|
||||||
{
|
|
||||||
private static $core_class = 'Solr3Service_Core';
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SolrConfigStore
|
|
||||||
*
|
|
||||||
* The interface Solr_Configure uses to upload configuration files to Solr
|
|
||||||
*/
|
|
||||||
interface SolrConfigStore
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Upload a file to Solr for index $index
|
|
||||||
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
|
||||||
* @param $file string - A path to a file to upload. The base name of the file will be used on the remote side
|
|
||||||
* @return null
|
|
||||||
*/
|
|
||||||
public function uploadFile($index, $file);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload a file to Solr from a string for index $index
|
|
||||||
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
|
||||||
* @param $filename string - The base name of the file to use on the remote side
|
|
||||||
* @param $strong string - The contents of the file
|
|
||||||
* @return null
|
|
||||||
*/
|
|
||||||
public function uploadString($index, $filename, $string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the instanceDir to tell Solr to use for index $index
|
|
||||||
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
|
||||||
*/
|
|
||||||
public function instanceDir($index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SolrConfigStore_File
|
|
||||||
*
|
|
||||||
* A ConfigStore that uploads files to a Solr instance on a locally accessible filesystem
|
|
||||||
* by just using file copies
|
|
||||||
*/
|
|
||||||
class SolrConfigStore_File implements SolrConfigStore
|
|
||||||
{
|
|
||||||
public function __construct($config)
|
|
||||||
{
|
|
||||||
$this->local = $config['path'];
|
|
||||||
$this->remote = isset($config['remotepath']) ? $config['remotepath'] : $config['path'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTargetDir($index)
|
|
||||||
{
|
|
||||||
$targetDir = "{$this->local}/{$index}/conf";
|
|
||||||
|
|
||||||
if (!is_dir($targetDir)) {
|
|
||||||
$worked = @mkdir($targetDir, 0770, true);
|
|
||||||
|
|
||||||
if (!$worked) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
sprintf('Failed creating target directory %s, please check permissions', $targetDir)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $targetDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadFile($index, $file)
|
|
||||||
{
|
|
||||||
$targetDir = $this->getTargetDir($index);
|
|
||||||
copy($file, $targetDir.'/'.basename($file));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadString($index, $filename, $string)
|
|
||||||
{
|
|
||||||
$targetDir = $this->getTargetDir($index);
|
|
||||||
file_put_contents("$targetDir/$filename", $string);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instanceDir($index)
|
|
||||||
{
|
|
||||||
return $this->remote.'/'.$index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SolrConfigStore_WebDAV
|
|
||||||
*
|
|
||||||
* A ConfigStore that uploads files to a Solr instance via a WebDAV server
|
|
||||||
*/
|
|
||||||
class SolrConfigStore_WebDAV implements SolrConfigStore
|
|
||||||
{
|
|
||||||
public function __construct($config)
|
|
||||||
{
|
|
||||||
$options = Solr::solr_options();
|
|
||||||
|
|
||||||
$this->url = implode('', array(
|
|
||||||
'http://',
|
|
||||||
isset($config['auth']) ? $config['auth'].'@' : '',
|
|
||||||
$options['host'].':'.(isset($config['port']) ? $config['port'] : $options['port']),
|
|
||||||
$config['path']
|
|
||||||
));
|
|
||||||
$this->remote = $config['remotepath'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTargetDir($index)
|
|
||||||
{
|
|
||||||
$indexdir = "{$this->url}/$index";
|
|
||||||
if (!WebDAV::exists($indexdir)) {
|
|
||||||
WebDAV::mkdir($indexdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
$targetDir = "{$this->url}/$index/conf";
|
|
||||||
if (!WebDAV::exists($targetDir)) {
|
|
||||||
WebDAV::mkdir($targetDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $targetDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadFile($index, $file)
|
|
||||||
{
|
|
||||||
$targetDir = $this->getTargetDir($index);
|
|
||||||
WebDAV::upload_from_file($file, $targetDir.'/'.basename($file));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadString($index, $filename, $string)
|
|
||||||
{
|
|
||||||
$targetDir = $this->getTargetDir($index);
|
|
||||||
WebDAV::upload_from_string($string, "$targetDir/$filename");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instanceDir($index)
|
|
||||||
{
|
|
||||||
return $this->remote ? "{$this->remote}/$index" : $index;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr;
|
||||||
|
|
||||||
Solr::include_client_api();
|
Solr::include_client_api();
|
||||||
|
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
|
use SilverStripe\FulltextSearch\Search\Indexes\SearchIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
||||||
|
use SilverStripe\FulltextSearch\Search\Queries\SearchQuery;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FulltextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\ORM\ArrayList;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\FieldType\DBField;
|
||||||
|
use SilverStripe\ORM\PaginatedList;
|
||||||
|
use SilverStripe\View\ArrayData;
|
||||||
|
|
||||||
abstract class SolrIndex extends SearchIndex
|
abstract class SolrIndex extends SearchIndex
|
||||||
{
|
{
|
||||||
public static $fulltextTypeMap = array(
|
public static $fulltextTypeMap = array(
|
||||||
@ -32,6 +47,11 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
|
|
||||||
protected $templatesPath = null;
|
protected $templatesPath = null;
|
||||||
|
|
||||||
|
private static $casting = [
|
||||||
|
'FieldDefinitions' => 'HTMLText',
|
||||||
|
'CopyFieldDefinitions' => 'HTMLText'
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of boosted fields
|
* List of boosted fields
|
||||||
*
|
*
|
||||||
@ -311,7 +331,7 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
public function setFieldBoosting($field, $level)
|
public function setFieldBoosting($field, $level)
|
||||||
{
|
{
|
||||||
if (!isset($this->fulltextFields[$field])) {
|
if (!isset($this->fulltextFields[$field])) {
|
||||||
throw new InvalidArgumentException("No fulltext field $field exists on ".$this->getIndexName());
|
throw new \InvalidArgumentException("No fulltext field $field exists on ".$this->getIndexName());
|
||||||
}
|
}
|
||||||
if ($level === null) {
|
if ($level === null) {
|
||||||
unset($this->boostedFields[$field]);
|
unset($this->boostedFields[$field]);
|
||||||
@ -474,7 +494,8 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
* @param array|string $base Class or list of base classes
|
* @param array|string $base Class or list of base classes
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function classIs($class, $base) {
|
protected function classIs($class, $base)
|
||||||
|
{
|
||||||
if (is_array($base)) {
|
if (is_array($base)) {
|
||||||
foreach ($base as $nextBase) {
|
foreach ($base as $nextBase) {
|
||||||
if ($this->classIs($class, $nextBase)) {
|
if ($this->classIs($class, $nextBase)) {
|
||||||
@ -530,15 +551,18 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only index fields that are not null
|
||||||
|
if ($value !== null) {
|
||||||
$doc->setField($field['name'], $value);
|
$doc->setField($field['name'], $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function _addAs($object, $base, $options)
|
protected function _addAs($object, $base, $options)
|
||||||
{
|
{
|
||||||
$includeSubs = $options['include_children'];
|
$includeSubs = $options['include_children'];
|
||||||
|
|
||||||
$doc = new Apache_Solr_Document();
|
$doc = new \Apache_Solr_Document();
|
||||||
|
|
||||||
// Always present fields
|
// Always present fields
|
||||||
|
|
||||||
@ -575,7 +599,7 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
|
|
||||||
foreach ($this->getClasses() as $searchclass => $options) {
|
foreach ($this->getClasses() as $searchclass => $options) {
|
||||||
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
||||||
$base = ClassInfo::baseDataClass($searchclass);
|
$base = DataObject::getSchema()->baseDataClass($searchclass);
|
||||||
$docs[] = $this->_addAs($object, $base, $options);
|
$docs[] = $this->_addAs($object, $base, $options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -740,7 +764,7 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
$offset,
|
$offset,
|
||||||
$limit,
|
$limit,
|
||||||
$params,
|
$params,
|
||||||
Apache_Solr_Service::METHOD_POST
|
\Apache_Solr_Service::METHOD_POST
|
||||||
);
|
);
|
||||||
|
|
||||||
$results = new ArrayList();
|
$results = new ArrayList();
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\Core\Environment;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DataList;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for re-indexing of solr content
|
* Base class for re-indexing of solr content
|
||||||
@ -24,7 +34,11 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
* @param string $classes
|
* @param string $classes
|
||||||
*/
|
*/
|
||||||
protected function processIndex(
|
protected function processIndex(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $batchSize, $taskName, $classes = null
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$batchSize,
|
||||||
|
$taskName,
|
||||||
|
$classes = null
|
||||||
) {
|
) {
|
||||||
// Filter classes for this index
|
// Filter classes for this index
|
||||||
$indexClasses = $this->getClassesForIndex($indexInstance, $classes);
|
$indexClasses = $this->getClassesForIndex($indexInstance, $classes);
|
||||||
@ -77,8 +91,13 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
* @param string $taskName
|
* @param string $taskName
|
||||||
*/
|
*/
|
||||||
protected function processVariant(
|
protected function processVariant(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state,
|
LoggerInterface $logger,
|
||||||
$class, $includeSubclasses, $batchSize, $taskName
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$includeSubclasses,
|
||||||
|
$batchSize,
|
||||||
|
$taskName
|
||||||
) {
|
) {
|
||||||
// Set state
|
// Set state
|
||||||
SearchVariant::activate_state($state);
|
SearchVariant::activate_state($state);
|
||||||
@ -117,7 +136,13 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
* @param string $taskName Name of task script to run
|
* @param string $taskName Name of task script to run
|
||||||
*/
|
*/
|
||||||
abstract protected function processGroup(
|
abstract protected function processGroup(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group, $taskName
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group,
|
||||||
|
$taskName
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,10 +159,15 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
* @param int $group
|
* @param int $group
|
||||||
*/
|
*/
|
||||||
public function runGroup(
|
public function runGroup(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group
|
||||||
) {
|
) {
|
||||||
// Set time limit and state
|
// Set time limit and state
|
||||||
increase_time_limit_to();
|
Environment::increaseTimeLimitTo();
|
||||||
SearchVariant::activate_state($state);
|
SearchVariant::activate_state($state);
|
||||||
$logger->info("Adding $class");
|
$logger->info("Adding $class");
|
||||||
|
|
||||||
@ -177,11 +207,11 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
protected function getRecordsInGroup(SolrIndex $indexInstance, $class, $groups, $group)
|
protected function getRecordsInGroup(SolrIndex $indexInstance, $class, $groups, $group)
|
||||||
{
|
{
|
||||||
// Generate filtered list of local records
|
// Generate filtered list of local records
|
||||||
$baseClass = ClassInfo::baseDataClass($class);
|
$baseClass = DataObject::getSchema()->baseDataClass($class);
|
||||||
$items = DataList::create($class)
|
$items = DataList::create($class)
|
||||||
->where(sprintf(
|
->where(sprintf(
|
||||||
'"%s"."ID" %% \'%d\' = \'%d\'',
|
'"%s"."ID" %% \'%d\' = \'%d\'',
|
||||||
$baseClass,
|
DataObject::getSchema()->tableName($baseClass),
|
||||||
intval($groups),
|
intval($groups),
|
||||||
intval($group)
|
intval($group)
|
||||||
))
|
))
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides interface for queueing a solr reindex
|
* Provides interface for queueing a solr reindex
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes an immediate reindex
|
* Invokes an immediate reindex
|
||||||
@ -15,7 +21,11 @@ class SolrReindexImmediateHandler extends SolrReindexBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function processIndex(
|
protected function processIndex(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $batchSize, $taskName, $classes = null
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$batchSize,
|
||||||
|
$taskName,
|
||||||
|
$classes = null
|
||||||
) {
|
) {
|
||||||
parent::processIndex($logger, $indexInstance, $batchSize, $taskName, $classes);
|
parent::processIndex($logger, $indexInstance, $batchSize, $taskName, $classes);
|
||||||
|
|
||||||
@ -40,7 +50,13 @@ class SolrReindexImmediateHandler extends SolrReindexBase
|
|||||||
* @param string $taskName Name of task script to run
|
* @param string $taskName Name of task script to run
|
||||||
*/
|
*/
|
||||||
protected function processGroup(
|
protected function processGroup(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group, $taskName
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group,
|
||||||
|
$taskName
|
||||||
) {
|
) {
|
||||||
// Build state
|
// Build state
|
||||||
$statevar = json_encode($state);
|
$statevar = json_encode($state);
|
||||||
@ -53,9 +69,12 @@ class SolrReindexImmediateHandler extends SolrReindexBase
|
|||||||
// Build script
|
// Build script
|
||||||
$indexName = $indexInstance->getIndexName();
|
$indexName = $indexInstance->getIndexName();
|
||||||
$indexClass = get_class($indexInstance);
|
$indexClass = get_class($indexInstance);
|
||||||
|
$indexClassEscaped = addslashes($indexClass);
|
||||||
|
$class = addslashes($class);
|
||||||
$scriptPath = sprintf("%s%sframework%scli-script.php", BASE_PATH, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
|
$scriptPath = sprintf("%s%sframework%scli-script.php", BASE_PATH, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
|
||||||
$scriptTask = "php {$scriptPath} dev/tasks/{$taskName}";
|
$scriptTask = "php {$scriptPath} dev/tasks/{$taskName}";
|
||||||
$cmd = "{$scriptTask} index={$indexClass} class={$class} group={$group} groups={$groups} variantstate={$statevar}";
|
|
||||||
|
$cmd = "{$scriptTask} index={$indexClassEscaped} class={$class} group={$group} groups={$groups} variantstate={$statevar}";
|
||||||
$cmd .= " verbose=1 2>&1";
|
$cmd .= " verbose=1 2>&1";
|
||||||
$logger->info("Running '$cmd'");
|
$logger->info("Running '$cmd'");
|
||||||
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
if (!class_exists('MessageQueue')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrReindexMessageHandler extends SolrReindexImmediateHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The MessageQueue to use when processing updates
|
|
||||||
* @config
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $reindex_queue = "search_indexing";
|
|
||||||
|
|
||||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
|
||||||
{
|
|
||||||
$queue = Config::inst()->get(__CLASS__, 'reindex_queue');
|
|
||||||
|
|
||||||
$logger->info('Queuing message');
|
|
||||||
MessageQueue::send(
|
|
||||||
$queue,
|
|
||||||
new MethodInvocationMessage('SolrReindexMessageHandler', 'run_reindex', $batchSize, $taskName, $classes)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entry point for message queue
|
|
||||||
*
|
|
||||||
* @param int $batchSize
|
|
||||||
* @param string $taskName
|
|
||||||
* @param array|string|null $classes
|
|
||||||
*/
|
|
||||||
public static function run_reindex($batchSize, $taskName, $classes = null)
|
|
||||||
{
|
|
||||||
// @todo Logger for message queue?
|
|
||||||
$logger = Injector::inst()->createWithArgs('Monolog\Logger', array(strtolower(get_class())));
|
|
||||||
|
|
||||||
$inst = Injector::inst()->get(get_class());
|
|
||||||
$inst->runReindex($logger, $batchSize, $taskName, $classes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers;
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\Core\Convert;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexQueuedJob;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexGroupQueuedJob;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateCommitJobProcessor;
|
||||||
|
|
||||||
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
use SilverStripe\QueuedJobs\DataObjects\QueuedJobDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a queued task to start the reindex job
|
* Represents a queued task to start the reindex job
|
||||||
*/
|
*/
|
||||||
@ -16,7 +30,7 @@ class SolrReindexQueuedHandler extends SolrReindexBase
|
|||||||
*/
|
*/
|
||||||
protected function getQueuedJobService()
|
protected function getQueuedJobService()
|
||||||
{
|
{
|
||||||
return singleton('QueuedJobService');
|
return singleton(QueuedJobService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,27 +54,28 @@ class SolrReindexQueuedHandler extends SolrReindexBase
|
|||||||
QueuedJob::STATUS_RUN
|
QueuedJob::STATUS_RUN
|
||||||
);
|
);
|
||||||
DB::query(sprintf(
|
DB::query(sprintf(
|
||||||
'UPDATE "QueuedJobDescriptor" '
|
'UPDATE "%s" '
|
||||||
. ' SET "JobStatus" = \'%s\''
|
. ' SET "JobStatus" = \'%s\''
|
||||||
. ' WHERE "JobStatus" IN (\'%s\')'
|
. ' WHERE "JobStatus" IN (\'%s\')'
|
||||||
. ' AND "Implementation" = \'%s\'',
|
. ' AND "Implementation" = \'%s\'',
|
||||||
|
Convert::raw2sql(DataObject::getSchema()->tableName(QueuedJobDescriptor::class)),
|
||||||
Convert::raw2sql(QueuedJob::STATUS_CANCELLED),
|
Convert::raw2sql(QueuedJob::STATUS_CANCELLED),
|
||||||
implode("','", Convert::raw2sql($clearable)),
|
implode("','", Convert::raw2sql($clearable)),
|
||||||
Convert::raw2sql($type)
|
Convert::raw2sql($type)
|
||||||
));
|
));
|
||||||
return DB::affectedRows();
|
return DB::affected_rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||||
{
|
{
|
||||||
// Cancel existing jobs
|
// Cancel existing jobs
|
||||||
$queues = $this->cancelExistingJobs('SolrReindexQueuedJob');
|
$queues = $this->cancelExistingJobs(SolrReindexQueuedJob::class);
|
||||||
$groups = $this->cancelExistingJobs('SolrReindexGroupQueuedJob');
|
$groups = $this->cancelExistingJobs(SolrReindexGroupQueuedJob::class);
|
||||||
$logger->info("Cancelled {$queues} re-index tasks and {$groups} re-index groups");
|
$logger->info("Cancelled {$queues} re-index tasks and {$groups} re-index groups");
|
||||||
|
|
||||||
// Although this class is used as a service (singleton) it may also be instantiated
|
// Although this class is used as a service (singleton) it may also be instantiated
|
||||||
// as a queuedjob
|
// as a queuedjob
|
||||||
$job = Injector::inst()->create('SolrReindexQueuedJob', $batchSize, $taskName, $classes);
|
$job = Injector::inst()->create(SolrReindexQueuedJob::class, $batchSize, $taskName, $classes);
|
||||||
$this
|
$this
|
||||||
->getQueuedJobService()
|
->getQueuedJobService()
|
||||||
->queueJob($job);
|
->queueJob($job);
|
||||||
@ -70,12 +85,22 @@ class SolrReindexQueuedHandler extends SolrReindexBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function processGroup(
|
protected function processGroup(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group, $taskName
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group,
|
||||||
|
$taskName
|
||||||
) {
|
) {
|
||||||
// Trigger another job for this group
|
// Trigger another job for this group
|
||||||
$job = Injector::inst()->create(
|
$job = Injector::inst()->create(
|
||||||
'SolrReindexGroupQueuedJob',
|
SolrReindexGroupQueuedJob::class,
|
||||||
get_class($indexInstance), $state, $class, $groups, $group
|
get_class($indexInstance),
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group
|
||||||
);
|
);
|
||||||
$this
|
$this
|
||||||
->getQueuedJobService()
|
->getQueuedJobService()
|
||||||
@ -86,7 +111,12 @@ class SolrReindexQueuedHandler extends SolrReindexBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function runGroup(
|
public function runGroup(
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group
|
||||||
) {
|
) {
|
||||||
parent::runGroup($logger, $indexInstance, $state, $class, $groups, $group);
|
parent::runGroup($logger, $indexInstance, $state, $class, $groups, $group);
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Jobs;
|
||||||
|
|
||||||
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Jobs;
|
||||||
|
|
||||||
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Reindex\Jobs;
|
||||||
|
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Utils\Logging\SearchLogFactory;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for jobs which perform re-index
|
* Base class for jobs which perform re-index
|
||||||
*/
|
*/
|
||||||
@ -44,7 +52,7 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob
|
|||||||
*/
|
*/
|
||||||
protected function getLoggerFactory()
|
protected function getLoggerFactory()
|
||||||
{
|
{
|
||||||
return Injector::inst()->get('SearchLogFactory');
|
return Injector::inst()->get(SearchLogFactory::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,7 +111,7 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob
|
|||||||
*/
|
*/
|
||||||
protected function getHandler()
|
protected function getHandler()
|
||||||
{
|
{
|
||||||
return Injector::inst()->get('SolrReindexHandler');
|
return Injector::inst()->get(SolrReindexHandler::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jobFinished()
|
public function jobFinished()
|
||||||
|
7
code/solr/services/Solr3Service.php
Normal file
7
code/solr/services/Solr3Service.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
|
class Solr3Service extends SolrService
|
||||||
|
{
|
||||||
|
private static $core_class = Solr3Service_Core::class;
|
||||||
|
}
|
7
code/solr/services/Solr3Service_Core.php
Normal file
7
code/solr/services/Solr3Service_Core.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
|
class Solr3Service_Core extends SolrService_Core
|
||||||
|
{
|
||||||
|
}
|
7
code/solr/services/Solr4Service.php
Normal file
7
code/solr/services/Solr4Service.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
|
class Solr4Service extends SolrService
|
||||||
|
{
|
||||||
|
private static $core_class = Solr4Service_Core::class;
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
class Solr4Service_Core extends SolrService_Core
|
class Solr4Service_Core extends SolrService_Core
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -23,8 +25,12 @@ class Solr4Service_Core extends SolrService_Core
|
|||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
* @see Solr4Service_Core::addDocuments
|
* @see Solr4Service_Core::addDocuments
|
||||||
*/
|
*/
|
||||||
public function addDocument(Apache_Solr_Document $document, $allowDups = false,
|
public function addDocument(
|
||||||
$overwritePending = true, $overwriteCommitted = true, $commitWithin = 0
|
\Apache_Solr_Document $document,
|
||||||
|
$allowDups = false,
|
||||||
|
$overwritePending = true,
|
||||||
|
$overwriteCommitted = true,
|
||||||
|
$commitWithin = 0
|
||||||
) {
|
) {
|
||||||
return $this->addDocuments(array($document), $allowDups, $overwritePending, $overwriteCommitted, $commitWithin);
|
return $this->addDocuments(array($document), $allowDups, $overwritePending, $overwriteCommitted, $commitWithin);
|
||||||
}
|
}
|
||||||
@ -33,8 +39,12 @@ class Solr4Service_Core extends SolrService_Core
|
|||||||
* Solr 4.0 compat http://wiki.apache.org/solr/UpdateXmlMessages#Optional_attributes_for_.22add.22
|
* Solr 4.0 compat http://wiki.apache.org/solr/UpdateXmlMessages#Optional_attributes_for_.22add.22
|
||||||
* Remove allowDups, overwritePending and overwriteComitted
|
* Remove allowDups, overwritePending and overwriteComitted
|
||||||
*/
|
*/
|
||||||
public function addDocuments($documents, $allowDups = false, $overwritePending = true,
|
public function addDocuments(
|
||||||
$overwriteCommitted = true, $commitWithin = 0
|
$documents,
|
||||||
|
$allowDups = false,
|
||||||
|
$overwritePending = true,
|
||||||
|
$overwriteCommitted = true,
|
||||||
|
$commitWithin = 0
|
||||||
) {
|
) {
|
||||||
$overwriteVal = $allowDups ? 'false' : 'true';
|
$overwriteVal = $allowDups ? 'false' : 'true';
|
||||||
$commitWithin = (int) $commitWithin;
|
$commitWithin = (int) $commitWithin;
|
||||||
@ -42,7 +52,7 @@ class Solr4Service_Core extends SolrService_Core
|
|||||||
|
|
||||||
$rawPost = "<add overwrite=\"{$overwriteVal}\"{$commitWithinString}>";
|
$rawPost = "<add overwrite=\"{$overwriteVal}\"{$commitWithinString}>";
|
||||||
foreach ($documents as $document) {
|
foreach ($documents as $document) {
|
||||||
if ($document instanceof Apache_Solr_Document) {
|
if ($document instanceof \Apache_Solr_Document) {
|
||||||
$rawPost .= $this->_documentToXmlFragment($document);
|
$rawPost .= $this->_documentToXmlFragment($document);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,8 +61,3 @@ class Solr4Service_Core extends SolrService_Core
|
|||||||
return $this->add($rawPost);
|
return $this->add($rawPost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Solr4Service extends SolrService
|
|
||||||
{
|
|
||||||
private static $core_class = 'Solr4Service_Core';
|
|
||||||
}
|
|
@ -1,14 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
|
use Silverstripe\Core\ClassInfo;
|
||||||
|
|
||||||
Solr::include_client_api();
|
Solr::include_client_api();
|
||||||
|
|
||||||
/**
|
|
||||||
* The API for accessing a specific core of a Solr server. Exactly the same as Apache_Solr_Service for now.
|
|
||||||
*/
|
|
||||||
class SolrService_Core extends Apache_Solr_Service
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The API for accessing the primary Solr installation, which includes both SolrService_Core,
|
* The API for accessing the primary Solr installation, which includes both SolrService_Core,
|
||||||
* plus extra methods for interrogating, creating, reloading and getting SolrService_Core instances
|
* plus extra methods for interrogating, creating, reloading and getting SolrService_Core instances
|
||||||
@ -16,7 +14,7 @@ class SolrService_Core extends Apache_Solr_Service
|
|||||||
*/
|
*/
|
||||||
class SolrService extends SolrService_Core
|
class SolrService extends SolrService_Core
|
||||||
{
|
{
|
||||||
private static $core_class = 'SolrService_Core';
|
private static $core_class = SolrService_Core::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle encoding the GET parameters and making the HTTP call to execute a core command
|
* Handle encoding the GET parameters and making the HTTP call to execute a core command
|
||||||
@ -24,7 +22,8 @@ class SolrService extends SolrService_Core
|
|||||||
protected function coreCommand($command, $core, $params = array())
|
protected function coreCommand($command, $core, $params = array())
|
||||||
{
|
{
|
||||||
$command = strtoupper($command);
|
$command = strtoupper($command);
|
||||||
|
//get the non-namespaced name of the Solr core, since backslashes not valid characters
|
||||||
|
$core = ClassInfo::shortName($core);
|
||||||
$params = array_merge($params, array('action' => $command, 'wt' => 'json'));
|
$params = array_merge($params, array('action' => $command, 'wt' => 'json'));
|
||||||
$params[$command == 'CREATE' ? 'name' : 'core'] = $core;
|
$params[$command == 'CREATE' ? 'name' : 'core'] = $core;
|
||||||
|
|
||||||
@ -85,6 +84,7 @@ class SolrService extends SolrService_Core
|
|||||||
public function serviceForCore($core)
|
public function serviceForCore($core)
|
||||||
{
|
{
|
||||||
$klass = Config::inst()->get(get_called_class(), 'core_class');
|
$klass = Config::inst()->get(get_called_class(), 'core_class');
|
||||||
return new $klass($this->_host, $this->_port, $this->_path.$core, $this->_httpTransport);
|
$coreName = ClassInfo::shortName($core);
|
||||||
|
return new $klass($this->_host, $this->_port, $this->_path . $coreName, $this->_httpTransport);
|
||||||
}
|
}
|
||||||
}
|
}
|
12
code/solr/services/SolrService_Core.php
Normal file
12
code/solr/services/SolrService_Core.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Services;
|
||||||
|
|
||||||
|
use \Apache_Solr_Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API for accessing a specific core of a Solr server. Exactly the same as Apache_Solr_Service for now.
|
||||||
|
*/
|
||||||
|
class SolrService_Core extends Apache_Solr_Service
|
||||||
|
{
|
||||||
|
}
|
33
code/solr/stores/SolrConfigStore.php
Normal file
33
code/solr/stores/SolrConfigStore.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Stores;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrConfigStore
|
||||||
|
*
|
||||||
|
* The interface Solr_Configure uses to upload configuration files to Solr
|
||||||
|
*/
|
||||||
|
interface SolrConfigStore
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Upload a file to Solr for index $index
|
||||||
|
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
||||||
|
* @param $file string - A path to a file to upload. The base name of the file will be used on the remote side
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function uploadFile($index, $file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a file to Solr from a string for index $index
|
||||||
|
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
||||||
|
* @param $filename string - The base name of the file to use on the remote side
|
||||||
|
* @param $strong string - The contents of the file
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function uploadString($index, $filename, $string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instanceDir to tell Solr to use for index $index
|
||||||
|
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
||||||
|
*/
|
||||||
|
public function instanceDir($index);
|
||||||
|
}
|
52
code/solr/stores/SolrConfigStore_File.php
Normal file
52
code/solr/stores/SolrConfigStore_File.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Stores;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrConfigStore_File
|
||||||
|
*
|
||||||
|
* A ConfigStore that uploads files to a Solr instance on a locally accessible filesystem
|
||||||
|
* by just using file copies
|
||||||
|
*/
|
||||||
|
class SolrConfigStore_File implements SolrConfigStore
|
||||||
|
{
|
||||||
|
public function __construct($config)
|
||||||
|
{
|
||||||
|
$this->local = $config['path'];
|
||||||
|
$this->remote = isset($config['remotepath']) ? $config['remotepath'] : $config['path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTargetDir($index)
|
||||||
|
{
|
||||||
|
$targetDir = "{$this->local}/{$index}/conf";
|
||||||
|
|
||||||
|
if (!is_dir($targetDir)) {
|
||||||
|
$worked = @mkdir($targetDir, 0770, true);
|
||||||
|
|
||||||
|
if (!$worked) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
sprintf('Failed creating target directory %s, please check permissions', $targetDir)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadFile($index, $file)
|
||||||
|
{
|
||||||
|
$targetDir = $this->getTargetDir($index);
|
||||||
|
copy($file, $targetDir.'/'.basename($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadString($index, $filename, $string)
|
||||||
|
{
|
||||||
|
$targetDir = $this->getTargetDir($index);
|
||||||
|
file_put_contents("$targetDir/$filename", $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instanceDir($index)
|
||||||
|
{
|
||||||
|
return $this->remote.'/'.$index;
|
||||||
|
}
|
||||||
|
}
|
59
code/solr/stores/SolrConfigStore_WebDAV.php
Normal file
59
code/solr/stores/SolrConfigStore_WebDAV.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Stores;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
|
use SilverStripe\FullTextSearch\Utils\WebDAV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrConfigStore_WebDAV
|
||||||
|
*
|
||||||
|
* A ConfigStore that uploads files to a Solr instance via a WebDAV server
|
||||||
|
*/
|
||||||
|
class SolrConfigStore_WebDAV implements SolrConfigStore
|
||||||
|
{
|
||||||
|
public function __construct($config)
|
||||||
|
{
|
||||||
|
$options = Solr::solr_options();
|
||||||
|
|
||||||
|
$this->url = implode('', array(
|
||||||
|
'http://',
|
||||||
|
isset($config['auth']) ? $config['auth'].'@' : '',
|
||||||
|
$options['host'].':'.(isset($config['port']) ? $config['port'] : $options['port']),
|
||||||
|
$config['path']
|
||||||
|
));
|
||||||
|
$this->remote = $config['remotepath'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTargetDir($index)
|
||||||
|
{
|
||||||
|
$indexdir = "{$this->url}/$index";
|
||||||
|
if (!WebDAV::exists($indexdir)) {
|
||||||
|
WebDAV::mkdir($indexdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetDir = "{$this->url}/$index/conf";
|
||||||
|
if (!WebDAV::exists($targetDir)) {
|
||||||
|
WebDAV::mkdir($targetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadFile($index, $file)
|
||||||
|
{
|
||||||
|
$targetDir = $this->getTargetDir($index);
|
||||||
|
WebDAV::upload_from_file($file, $targetDir.'/'.basename($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadString($index, $filename, $string)
|
||||||
|
{
|
||||||
|
$targetDir = $this->getTargetDir($index);
|
||||||
|
WebDAV::upload_from_string($string, "$targetDir/$filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instanceDir($index)
|
||||||
|
{
|
||||||
|
return $this->remote ? "{$this->remote}/$index" : $index;
|
||||||
|
}
|
||||||
|
}
|
67
code/solr/tasks/Solr_BuildTask.php
Normal file
67
code/solr/tasks/Solr_BuildTask.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Tasks;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Dev\BuildTask;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Utils\Logging\SearchLogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for build tasks
|
||||||
|
*/
|
||||||
|
class Solr_BuildTask extends BuildTask
|
||||||
|
{
|
||||||
|
protected $enabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current logger
|
||||||
|
*
|
||||||
|
* @return LoggerInterface
|
||||||
|
*/
|
||||||
|
public function getLogger()
|
||||||
|
{
|
||||||
|
return Injector::inst()->get(LoggerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a new logger
|
||||||
|
*
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function setLogger(LoggerInterface $logger)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchLogFactory
|
||||||
|
*/
|
||||||
|
protected function getLoggerFactory()
|
||||||
|
{
|
||||||
|
return Injector::inst()->get(SearchLogFactory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup task
|
||||||
|
*
|
||||||
|
* @param SS_HTTPReqest $request
|
||||||
|
*/
|
||||||
|
public function run($request)
|
||||||
|
{
|
||||||
|
$name = get_class($this);
|
||||||
|
$verbose = $request->getVar('verbose');
|
||||||
|
|
||||||
|
// Set new logger
|
||||||
|
$logger = $this
|
||||||
|
->getLoggerFactory()
|
||||||
|
->getOutputLogger($name, $verbose);
|
||||||
|
$this->setLogger($logger);
|
||||||
|
}
|
||||||
|
}
|
94
code/solr/tasks/Solr_Configure.php
Normal file
94
code/solr/tasks/Solr_Configure.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Tasks;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Stores\SolrConfigStore_File;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Stores\SolrConfigStore_WebDAV;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Stores\SolrConfigStore;
|
||||||
|
|
||||||
|
class Solr_Configure extends Solr_BuildTask
|
||||||
|
{
|
||||||
|
private static $segment = 'Solr_Configure';
|
||||||
|
protected $enabled = true;
|
||||||
|
|
||||||
|
public function run($request)
|
||||||
|
{
|
||||||
|
parent::run($request);
|
||||||
|
|
||||||
|
// Find the IndexStore handler, which will handle uploading config files to Solr
|
||||||
|
$store = $this->getSolrConfigStore();
|
||||||
|
|
||||||
|
$indexes = Solr::get_indexes();
|
||||||
|
foreach ($indexes as $instance) {
|
||||||
|
try {
|
||||||
|
$this->updateIndex($instance, $store);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// We got an exception. Warn, but continue to next index.
|
||||||
|
$this
|
||||||
|
->getLogger()
|
||||||
|
->error("Failure: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($e)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the index on the given store
|
||||||
|
*
|
||||||
|
* @param SolrIndex $instance Instance
|
||||||
|
* @param SolrConfigStore $store
|
||||||
|
*/
|
||||||
|
protected function updateIndex($instance, $store)
|
||||||
|
{
|
||||||
|
$index = $instance->getIndexName();
|
||||||
|
$this->getLogger()->addInfo("Configuring $index.");
|
||||||
|
|
||||||
|
// Upload the config files for this index
|
||||||
|
$this->getLogger()->addInfo("Uploading configuration ...");
|
||||||
|
$instance->uploadConfig($store);
|
||||||
|
|
||||||
|
// Then tell Solr to use those config files
|
||||||
|
$service = Solr::service();
|
||||||
|
if ($service->coreIsActive($index)) {
|
||||||
|
$this->getLogger()->addInfo("Reloading core ...");
|
||||||
|
$service->coreReload($index);
|
||||||
|
} else {
|
||||||
|
$this->getLogger()->addInfo("Creating core ...");
|
||||||
|
$service->coreCreate($index, $store->instanceDir($index));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getLogger()->addInfo("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get config store
|
||||||
|
*
|
||||||
|
* @return SolrConfigStore
|
||||||
|
*/
|
||||||
|
protected function getSolrConfigStore()
|
||||||
|
{
|
||||||
|
$options = Solr::solr_options();
|
||||||
|
|
||||||
|
if (!isset($options['indexstore']) || !($indexstore = $options['indexstore'])) {
|
||||||
|
throw new Exception('No index configuration for Solr provided', E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the IndexStore handler, which will handle uploading config files to Solr
|
||||||
|
$mode = $indexstore['mode'];
|
||||||
|
|
||||||
|
if ($mode == 'file') {
|
||||||
|
return new SolrConfigStore_File($indexstore);
|
||||||
|
} elseif ($mode == 'webdav') {
|
||||||
|
return new SolrConfigStore_WebDAV($indexstore);
|
||||||
|
} elseif (ClassInfo::exists($mode) && ClassInfo::classImplements($mode, SolrConfigStore::class)) {
|
||||||
|
return new $mode($indexstore);
|
||||||
|
} else {
|
||||||
|
user_error('Unknown Solr index mode '.$indexstore['mode'], E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
code/solr/tasks/Solr_Reindex.php
Normal file
158
code/solr/tasks/Solr_Reindex.php
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Tasks;
|
||||||
|
|
||||||
|
use ReflectionClass;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Dev\Debug;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\ORM\DataList;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task used for both initiating a new reindex, as well as for processing incremental batches
|
||||||
|
* within a reindex.
|
||||||
|
*
|
||||||
|
* When running a complete reindex you can provide any of the following
|
||||||
|
* - class (to limit to a single class)
|
||||||
|
* - verbose (optional)
|
||||||
|
*
|
||||||
|
* When running with a single batch, provide the following querystring arguments:
|
||||||
|
* - start
|
||||||
|
* - index
|
||||||
|
* - class
|
||||||
|
* - variantstate
|
||||||
|
* - verbose (optional)
|
||||||
|
*/
|
||||||
|
class Solr_Reindex extends Solr_BuildTask
|
||||||
|
{
|
||||||
|
private static $segment = 'Solr_Reindex';
|
||||||
|
|
||||||
|
protected $enabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of records to load and index per request
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @config
|
||||||
|
*/
|
||||||
|
private static $recordsPerRequest = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reindex handler
|
||||||
|
*
|
||||||
|
* @return SolrReindexHandler
|
||||||
|
*/
|
||||||
|
protected function getHandler()
|
||||||
|
{
|
||||||
|
return Injector::inst()->get(SolrReindexHandler::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*/
|
||||||
|
public function run($request)
|
||||||
|
{
|
||||||
|
parent::run($request);
|
||||||
|
|
||||||
|
// Reset state
|
||||||
|
$originalState = SearchVariant::current_state();
|
||||||
|
$this->doReindex($request);
|
||||||
|
SearchVariant::activate_state($originalState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*/
|
||||||
|
protected function doReindex($request)
|
||||||
|
{
|
||||||
|
$class = $request->getVar('class');
|
||||||
|
|
||||||
|
$index = $request->getVar('index');
|
||||||
|
|
||||||
|
//find the index classname by IndexName
|
||||||
|
// this is for when index names do not match the class name (this can be done by overloading getIndexName() on
|
||||||
|
// indexes
|
||||||
|
if ($index && !ClassInfo::exists($index)) {
|
||||||
|
foreach (ClassInfo::subclassesFor(SolrIndex::class) as $solrIndexClass) {
|
||||||
|
$reflection = new ReflectionClass($solrIndexClass);
|
||||||
|
//skip over abstract classes
|
||||||
|
if (!$reflection->isInstantiable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//check the indexname matches the index passed to the request
|
||||||
|
if (!strcasecmp(singleton($solrIndexClass)->getIndexName(), $index)) {
|
||||||
|
//if we match, set the correct index name and move on
|
||||||
|
$index = $solrIndexClass;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated reindex mechanism
|
||||||
|
$start = $request->getVar('start');
|
||||||
|
if ($start !== null) {
|
||||||
|
// Run single batch directly
|
||||||
|
$indexInstance = singleton($index);
|
||||||
|
$state = json_decode($request->getVar('variantstate'), true);
|
||||||
|
$this->runFrom($indexInstance, $class, $start, $state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are re-indexing a single group
|
||||||
|
// If not using queuedjobs, we need to invoke Solr_Reindex as a separate process
|
||||||
|
// Otherwise each group is processed via a SolrReindexGroupJob
|
||||||
|
$groups = $request->getVar('groups');
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
if ($groups) {
|
||||||
|
// Run grouped batches (id % groups = group)
|
||||||
|
$group = $request->getVar('group');
|
||||||
|
$indexInstance = singleton($index);
|
||||||
|
$state = json_decode($request->getVar('variantstate'), true);
|
||||||
|
|
||||||
|
$handler->runGroup($this->getLogger(), $indexInstance, $state, $class, $groups, $group);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If run at the top level, delegate to appropriate handler
|
||||||
|
$taskName = $this->config()->segment ?: get_class($this);
|
||||||
|
$handler->triggerReindex($this->getLogger(), $this->config()->recordsPerRequest, $taskName, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since version 2.0.0
|
||||||
|
*/
|
||||||
|
protected function runFrom($index, $class, $start, $variantstate)
|
||||||
|
{
|
||||||
|
DeprecationTest_Deprecation::notice('2.0.0', 'Solr_Reindex now uses a new grouping mechanism');
|
||||||
|
|
||||||
|
// Set time limit and state
|
||||||
|
increase_time_limit_to();
|
||||||
|
SearchVariant::activate_state($variantstate);
|
||||||
|
|
||||||
|
// Generate filtered list
|
||||||
|
$items = DataList::create($class)
|
||||||
|
->limit($this->config()->recordsPerRequest, $start);
|
||||||
|
|
||||||
|
// Add child filter
|
||||||
|
$classes = $index->getClasses();
|
||||||
|
$options = $classes[$class];
|
||||||
|
if (!$options['include_children']) {
|
||||||
|
$items = $items->filter('ClassName', $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process selected records in this class
|
||||||
|
$this->getLogger()->info("Adding $class");
|
||||||
|
foreach ($items->sort("ID") as $item) {
|
||||||
|
$this->getLogger()->debug($item->ID);
|
||||||
|
|
||||||
|
// See SearchUpdater_ObjectHandler::triggerReindex
|
||||||
|
$item->triggerReindex();
|
||||||
|
$item->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getLogger()->info("Done");
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
class CombinationsArrayIterator implements Iterator
|
class CombinationsArrayIterator implements Iterator
|
||||||
{
|
{
|
||||||
protected $arrays;
|
protected $arrays;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
class MultipleArrayIterator implements Iterator
|
class MultipleArrayIterator implements Iterator
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils;
|
||||||
|
|
||||||
class WebDAV
|
class WebDAV
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils\Logging;
|
||||||
|
|
||||||
use Monolog\Formatter\FormatterInterface;
|
use Monolog\Formatter\FormatterInterface;
|
||||||
use Monolog\Formatter\LineFormatter;
|
use Monolog\Formatter\LineFormatter;
|
||||||
use Monolog\Handler\HandlerInterface;
|
use Monolog\Handler\HandlerInterface;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides logging based on monolog
|
* Provides logging based on monolog
|
||||||
@ -97,7 +101,7 @@ class MonologFactory implements SearchLogFactory
|
|||||||
protected function getJobHandler($job)
|
protected function getJobHandler($job)
|
||||||
{
|
{
|
||||||
return Injector::inst()->createWithArgs(
|
return Injector::inst()->createWithArgs(
|
||||||
'QueuedJobLogHandler',
|
'SilverStripe\FullTextSearch\Utils\Logging\QueuedJobLogHandler',
|
||||||
array($job, Logger::INFO)
|
array($job, Logger::INFO)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils\Logging;
|
||||||
|
|
||||||
use Monolog\Handler\AbstractProcessingHandler;
|
use Monolog\Handler\AbstractProcessingHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for logging events into QueuedJob message data
|
* Handler for logging events into QueuedJob message data
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Utils\Logging;
|
||||||
|
|
||||||
use Psr\Log;
|
use Psr\Log;
|
||||||
|
|
||||||
interface SearchLogFactory
|
interface SearchLogFactory
|
||||||
|
1
codecov.yml
Normal file
1
codecov.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
comment: false
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "silverstripe/fulltextsearch",
|
"name": "silverstripe/fulltextsearch",
|
||||||
"description": "Adds support for Fulltext Search engines like Sphinx and Solr to SilverStripe CMS",
|
"description": "Adds support for Fulltext Search engines like Sphinx and Solr to SilverStripe CMS",
|
||||||
"type": "silverstripe-module",
|
"type": "silverstripe-vendormodule",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"silverstripe",
|
"silverstripe",
|
||||||
"sphinx",
|
"sphinx",
|
||||||
@ -20,12 +21,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"silverstripe/framework": "~3.1",
|
"silverstripe/framework": "^4.0",
|
||||||
"monolog/monolog": "~1.15"
|
"monolog/monolog": "~1.15"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"silverstripe/cms": "~3.1",
|
"silverstripe/cms": "^4.0",
|
||||||
"hafriedlander/silverstripe-phockito": "*"
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"squizlabs/php_codesniffer": "^3.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SilverStripe\\FullTextSearch\\": "code/",
|
||||||
|
"SilverStripe\\FullTextSearch\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
@ -36,5 +44,5 @@
|
|||||||
"silverstripe/fulltextsearch-localsolr": "Adds a ready-to-use local Solr server for initial development"
|
"silverstripe/fulltextsearch-localsolr": "Adds a ready-to-use local Solr server for initial development"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"license": "BSD-3-Clause"
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
@ -56,16 +56,16 @@ All possible parameters incl optional ones with example values:
|
|||||||
Solr::configure_server(array(
|
Solr::configure_server(array(
|
||||||
'host' => 'localhost', // default: localhost | The host or IP Solr is listening on
|
'host' => 'localhost', // default: localhost | The host or IP Solr is listening on
|
||||||
'port' => '8983', // default: 8983 | The port Solr is listening on
|
'port' => '8983', // default: 8983 | The port Solr is listening on
|
||||||
'path' => '/solr' // default: /solr | The suburl the solr service is available on
|
'path' => '/solr', // default: /solr | The suburl the solr service is available on
|
||||||
'version' => '4' // default: 4 | Solr server version - currently only 3 and 4 supported
|
'version' => '4', // default: 4 | Solr server version - currently only 3 and 4 supported
|
||||||
'service' => 'Solr4Service' // default: depends on version, Solr3Service for 3, Solr4Service for 4 | the class that provides actual communcation to the Solr server
|
'service' => 'Solr4Service', // default: depends on version, Solr3Service for 3, Solr4Service for 4 | the class that provides actual communcation to the Solr server
|
||||||
'extraspath' => BASE_PATH .'/fulltextsearch/conf/solr/4/extras/' // default: <basefolder>/fulltextsearch/conf/solr/{version}/extras/ | Absolute path to the folder containing templates which are used for generating the schema and field definitions.
|
'extraspath' => BASE_PATH .'/fulltextsearch/conf/solr/4/extras/', // default: <basefolder>/fulltextsearch/conf/solr/{version}/extras/ | Absolute path to the folder containing templates which are used for generating the schema and field definitions.
|
||||||
'templates' => BASE_PATH . '/fulltextsearch/conf/solr/4/templates/' // default: <basefolder>/fulltextsearch/conf/solr/{version}/templates/ | Absolute path to the configuration default files, e.g. solrconfig.xml
|
'templates' => BASE_PATH . '/fulltextsearch/conf/solr/4/templates/', // default: <basefolder>/fulltextsearch/conf/solr/{version}/templates/ | Absolute path to the configuration default files, e.g. solrconfig.xml
|
||||||
'indexstore' => array(
|
'indexstore' => array(
|
||||||
'mode' => 'file', // a classname which implements SolrConfigStore, or 'file' or 'webdav'
|
'mode' => 'file', // a classname which implements SolrConfigStore, or 'file' or 'webdav'
|
||||||
'path' => BASE_PATH . '/.solr' // The (locally accessible) path to write the index configurations to OR The suburl on the solr host that is set up to accept index configurations via webdav
|
'path' => BASE_PATH . '/.solr', // The (locally accessible) path to write the index configurations to OR The suburl on the solr host that is set up to accept index configurations via webdav
|
||||||
'remotepath' => '/opt/solr/config' // default (file mode only): same as 'path' above | The path that the Solr server will read the index configurations from
|
'remotepath' => '/opt/solr/config', // default (file mode only): same as 'path' above | The path that the Solr server will read the index configurations from
|
||||||
'auth' => 'solr:solr' // default: none | Webdav only - A username:password pair string to use to auth against the webdav server
|
'auth' => 'solr:solr', // default: none | Webdav only - A username:password pair string to use to auth against the webdav server
|
||||||
'port' => '80' // default: same as solr port | The port for WebDAV if different from the Solr port
|
'port' => '80' // default: same as solr port | The port for WebDAV if different from the Solr port
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
14
phpunit.xml.dist
Normal file
14
phpunit.xml.dist
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<phpunit bootstrap="vendor/silverstripe/cms/tests/bootstrap.php" colors="true">
|
||||||
|
<testsuite name="Default">
|
||||||
|
<directory>tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||||
|
<directory suffix=".php">code/</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory suffix=".php">tests/</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
@ -1,40 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
class BatchedProcessorTest_Object extends SiteTree implements TestOnly
|
use SilverStripe\Dev\SapphireTest;
|
||||||
{
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
private static $db = array(
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
'TestText' => 'Varchar'
|
use SilverStripe\Core\Config\Config;
|
||||||
);
|
use SilverStripe\Versioned\Versioned;
|
||||||
}
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessor_QueuedJobService;
|
||||||
class BatchedProcessorTest_Index extends SearchIndex_Recording implements TestOnly
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Index;
|
||||||
{
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Object;
|
||||||
public function init()
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateCommitJobProcessor;
|
||||||
{
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateQueuedJobProcessor;
|
||||||
$this->addClass('BatchedProcessorTest_Object');
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateBatchedProcessor;
|
||||||
$this->addFilterField('TestText');
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
}
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
}
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
|
||||||
class BatchedProcessor_QueuedJobService
|
|
||||||
{
|
|
||||||
protected $jobs = array();
|
|
||||||
|
|
||||||
public function queueJob(QueuedJob $job, $startAfter = null, $userId = null, $queueName = null)
|
|
||||||
{
|
|
||||||
$this->jobs[] = array(
|
|
||||||
'job' => $job,
|
|
||||||
'startAfter' => $startAfter
|
|
||||||
);
|
|
||||||
return $job;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getJobs()
|
|
||||||
{
|
|
||||||
return $this->jobs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@see SearchUpdateQueuedJobProcessor}
|
* Tests {@see SearchUpdateQueuedJobProcessor}
|
||||||
@ -43,8 +25,8 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
protected $oldProcessor;
|
protected $oldProcessor;
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected static $extra_dataobjects = array(
|
||||||
'BatchedProcessorTest_Object'
|
BatchedProcessorTest_Object::class
|
||||||
);
|
);
|
||||||
|
|
||||||
protected $illegalExtensions = array(
|
protected $illegalExtensions = array(
|
||||||
@ -57,7 +39,7 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
public function setUpOnce()
|
public function setUpOnce()
|
||||||
{
|
{
|
||||||
// Disable illegal extensions if skipping this test
|
// Disable illegal extensions if skipping this test
|
||||||
if (class_exists('Subsite') || !interface_exists('QueuedJob')) {
|
if (class_exists('Subsite') || !interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
$this->illegalExtensions = array();
|
$this->illegalExtensions = array();
|
||||||
}
|
}
|
||||||
parent::setUpOnce();
|
parent::setUpOnce();
|
||||||
@ -67,7 +49,7 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
$this->skipTest = true;
|
$this->skipTest = true;
|
||||||
$this->markTestSkipped("These tests need the QueuedJobs module installed to run");
|
$this->markTestSkipped("These tests need the QueuedJobs module installed to run");
|
||||||
}
|
}
|
||||||
@ -77,17 +59,17 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
$this->markTestSkipped(get_class() . ' skipped when running with subsites');
|
$this->markTestSkipped(get_class() . ' skipped when running with subsites');
|
||||||
}
|
}
|
||||||
|
|
||||||
SS_Datetime::set_mock_now('2015-05-07 06:00:00');
|
DBDatetime::set_mock_now('2015-05-07 06:00:00');
|
||||||
|
|
||||||
Config::inst()->update('SearchUpdateBatchedProcessor', 'batch_size', 5);
|
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_size', 5);
|
||||||
Config::inst()->update('SearchUpdateBatchedProcessor', 'batch_soft_cap', 0);
|
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 0);
|
||||||
Config::inst()->update('SearchUpdateCommitJobProcessor', 'cooldown', 600);
|
Config::modify()->set(SearchUpdateCommitJobProcessor::class, 'cooldown', 600);
|
||||||
|
|
||||||
Versioned::reading_stage("Stage");
|
Versioned::set_stage("Stage");
|
||||||
|
|
||||||
Injector::inst()->registerService(new BatchedProcessor_QueuedJobService(), 'QueuedJobService');
|
Injector::inst()->registerService(new BatchedProcessor_QueuedJobService(), QueuedJobService::class);
|
||||||
|
|
||||||
FullTextSearch::force_index_list('BatchedProcessorTest_Index');
|
FullTextSearch::force_index_list(BatchedProcessorTest_Index::class);
|
||||||
|
|
||||||
SearchUpdateCommitJobProcessor::$dirty_indexes = array();
|
SearchUpdateCommitJobProcessor::$dirty_indexes = array();
|
||||||
SearchUpdateCommitJobProcessor::$has_run = false;
|
SearchUpdateCommitJobProcessor::$has_run = false;
|
||||||
@ -118,12 +100,12 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
$object->write();
|
$object->write();
|
||||||
// Add to index manually
|
// Add to index manually
|
||||||
$processor->addDirtyIDs(
|
$processor->addDirtyIDs(
|
||||||
'BatchedProcessorTest_Object',
|
BatchedProcessorTest_Object::class,
|
||||||
array(array(
|
array(array(
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'state' => array('SearchVariantVersioned' => 'Stage')
|
'state' => array(SearchVariantVersioned::class => 'Stage')
|
||||||
)),
|
)),
|
||||||
'BatchedProcessorTest_Index'
|
BatchedProcessorTest_Index::class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$processor->batchData();
|
$processor->batchData();
|
||||||
@ -135,7 +117,7 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testBatching()
|
public function testBatching()
|
||||||
{
|
{
|
||||||
$index = singleton('BatchedProcessorTest_Index');
|
$index = singleton(BatchedProcessorTest_Index::class);
|
||||||
$index->reset();
|
$index->reset();
|
||||||
$processor = $this->generateDirtyIds();
|
$processor = $this->generateDirtyIds();
|
||||||
|
|
||||||
@ -163,10 +145,10 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
|
|
||||||
// Check any additional queued jobs
|
// Check any additional queued jobs
|
||||||
$processor->afterComplete();
|
$processor->afterComplete();
|
||||||
$service = singleton('QueuedJobService');
|
$service = singleton(QueuedJobService::class);
|
||||||
$jobs = $service->getJobs();
|
$jobs = $service->getJobs();
|
||||||
$this->assertEquals(1, count($jobs));
|
$this->assertEquals(1, count($jobs));
|
||||||
$this->assertInstanceOf('SearchUpdateCommitJobProcessor', $jobs[0]['job']);
|
$this->assertInstanceOf(SearchUpdateCommitJobProcessor::class, $jobs[0]['job']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -174,7 +156,7 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testMultipleCommits()
|
public function testMultipleCommits()
|
||||||
{
|
{
|
||||||
$index = singleton('BatchedProcessorTest_Index');
|
$index = singleton(BatchedProcessorTest_Index::class);
|
||||||
$index->reset();
|
$index->reset();
|
||||||
|
|
||||||
// Test that running a commit immediately after submitting to the indexes
|
// Test that running a commit immediately after submitting to the indexes
|
||||||
@ -221,25 +203,25 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testSoftCap()
|
public function testSoftCap()
|
||||||
{
|
{
|
||||||
$index = singleton('BatchedProcessorTest_Index');
|
$index = singleton(BatchedProcessorTest_Index::class);
|
||||||
$index->reset();
|
$index->reset();
|
||||||
$processor = $this->generateDirtyIds();
|
$processor = $this->generateDirtyIds();
|
||||||
|
|
||||||
// Test that increasing the soft cap to 2 will reduce the number of batches
|
// Test that increasing the soft cap to 2 will reduce the number of batches
|
||||||
Config::inst()->update('SearchUpdateBatchedProcessor', 'batch_soft_cap', 2);
|
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 2);
|
||||||
$processor->batchData();
|
$processor->batchData();
|
||||||
$data = $processor->getJobData();
|
$data = $processor->getJobData();
|
||||||
//Debug::dump($data);die;
|
//Debug::dump($data);die;
|
||||||
$this->assertEquals(8, $data->totalSteps);
|
$this->assertEquals(8, $data->totalSteps);
|
||||||
|
|
||||||
// A soft cap of 1 should not fit in the hanging two items
|
// A soft cap of 1 should not fit in the hanging two items
|
||||||
Config::inst()->update('SearchUpdateBatchedProcessor', 'batch_soft_cap', 1);
|
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 1);
|
||||||
$processor->batchData();
|
$processor->batchData();
|
||||||
$data = $processor->getJobData();
|
$data = $processor->getJobData();
|
||||||
$this->assertEquals(9, $data->totalSteps);
|
$this->assertEquals(9, $data->totalSteps);
|
||||||
|
|
||||||
// Extra large soft cap should fit both items
|
// Extra large soft cap should fit both items
|
||||||
Config::inst()->update('SearchUpdateBatchedProcessor', 'batch_soft_cap', 4);
|
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 4);
|
||||||
$processor->batchData();
|
$processor->batchData();
|
||||||
$data = $processor->getJobData();
|
$data = $processor->getJobData();
|
||||||
$this->assertEquals(8, $data->totalSteps);
|
$this->assertEquals(8, $data->totalSteps);
|
||||||
|
15
tests/BatchedProcessorTest/BatchedProcessorTest_Index.php
Normal file
15
tests/BatchedProcessorTest/BatchedProcessorTest_Index.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\BatchedProcessorTest;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
|
||||||
|
class BatchedProcessorTest_Index extends SearchIndex_Recording implements TestOnly
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(BatchedProcessorTest_Object::class);
|
||||||
|
$this->addFilterField('TestText');
|
||||||
|
}
|
||||||
|
}
|
15
tests/BatchedProcessorTest/BatchedProcessorTest_Object.php
Normal file
15
tests/BatchedProcessorTest/BatchedProcessorTest_Object.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\BatchedProcessorTest;
|
||||||
|
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
|
class BatchedProcessorTest_Object extends SiteTree implements TestOnly
|
||||||
|
{
|
||||||
|
private static $table_name = 'BatchedProcessorTest_Object';
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'TestText' => 'Varchar'
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\BatchedProcessorTest;
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
|
||||||
|
class BatchedProcessor_QueuedJobService
|
||||||
|
{
|
||||||
|
protected $jobs = array();
|
||||||
|
|
||||||
|
public function queueJob(QueuedJob $job, $startAfter = null, $userId = null, $queueName = null)
|
||||||
|
{
|
||||||
|
$this->jobs[] = array(
|
||||||
|
'job' => $job,
|
||||||
|
'startAfter' => $startAfter
|
||||||
|
);
|
||||||
|
return $job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJobs()
|
||||||
|
{
|
||||||
|
return $this->jobs;
|
||||||
|
}
|
||||||
|
}
|
@ -1,99 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class SearchUpdaterTest_Container extends DataObject
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
{
|
|
||||||
private static $db = array(
|
|
||||||
'Field1' => 'Varchar',
|
|
||||||
'Field2' => 'Varchar',
|
|
||||||
'MyDate' => 'Date',
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $has_one = array(
|
use SilverStripe\Core\Config\Config;
|
||||||
'HasOneObject' => 'SearchUpdaterTest_HasOne'
|
use SilverStripe\Core\Injector\Injector;
|
||||||
);
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
private static $has_many = array(
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
||||||
'HasManyObjects' => 'SearchUpdaterTest_HasMany'
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
);
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
private static $many_many = array(
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
|
||||||
'ManyManyObjects' => 'SearchUpdaterTest_ManyMany'
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany;
|
||||||
);
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Index;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to test ambiguous relationships.
|
|
||||||
*/
|
|
||||||
class SearchUpdaterTest_OtherContainer extends DataObject
|
|
||||||
{
|
|
||||||
private static $has_many = array(
|
|
||||||
'HasManyObjects' => 'SearchUpdaterTest_HasMany'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $many_many = array(
|
|
||||||
'ManyManyObjects' => 'SearchUpdaterTest_ManyMany'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to test inherited ambiguous relationships.
|
|
||||||
*/
|
|
||||||
class SearchUpdaterTest_ExtendedContainer extends SearchUpdaterTest_OtherContainer
|
|
||||||
{
|
|
||||||
private static $db = array(
|
|
||||||
'SomeField' => 'Varchar',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchUpdaterTest_HasOne extends DataObject
|
|
||||||
{
|
|
||||||
private static $db = array(
|
|
||||||
'Field1' => 'Varchar',
|
|
||||||
'Field2' => 'Varchar'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $has_many = array(
|
|
||||||
'HasManyContainers' => 'SearchUpdaterTest_Container'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchUpdaterTest_HasMany extends DataObject
|
|
||||||
{
|
|
||||||
private static $db = array(
|
|
||||||
'Field1' => 'Varchar',
|
|
||||||
'Field2' => 'Varchar'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $has_one = array(
|
|
||||||
'HasManyContainer' => 'SearchUpdaterTest_Container',
|
|
||||||
'HasManyOtherContainer' => 'SearchUpdaterTest_OtherContainer',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchUpdaterTest_ManyMany extends DataObject
|
|
||||||
{
|
|
||||||
private static $db = array(
|
|
||||||
'Field1' => 'Varchar',
|
|
||||||
'Field2' => 'Varchar'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $belongs_many_many = array(
|
|
||||||
'ManyManyContainer' => 'SearchUpdaterTest_Container',
|
|
||||||
'ManyManyOtherContainer' => 'SearchUpdaterTest_OtherContainer',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchUpdaterTest_Index extends SearchIndex_Recording
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
|
|
||||||
$this->addFilterField('Field1');
|
|
||||||
$this->addFilterField('HasOneObject.Field1');
|
|
||||||
$this->addFilterField('HasManyObjects.Field1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchUpdaterTest extends SapphireTest
|
class SearchUpdaterTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -101,20 +20,22 @@ class SearchUpdaterTest extends SapphireTest
|
|||||||
|
|
||||||
private static $index = null;
|
private static $index = null;
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (self::$index === null) {
|
if (self::$index === null) {
|
||||||
self::$index = singleton(get_class($this).'_Index');
|
self::$index = SearchUpdaterTest_Index::singleton();
|
||||||
} else {
|
} else {
|
||||||
self::$index->reset();
|
self::$index->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchUpdater::bind_manipulation_capture();
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
|
||||||
Config::inst()->update('Injector', 'SearchUpdateProcessor', array(
|
Config::modify()->set(Injector::class, SearchUpdateProcessor::class, array(
|
||||||
'class' => 'SearchUpdateImmediateProcessor'
|
'class' => SearchUpdateImmediateProcessor::class
|
||||||
));
|
));
|
||||||
|
|
||||||
FullTextSearch::force_index_list(self::$index);
|
FullTextSearch::force_index_list(self::$index);
|
||||||
@ -153,10 +74,11 @@ class SearchUpdaterTest extends SapphireTest
|
|||||||
// Check the default "writing a document updates the document"
|
// Check the default "writing a document updates the document"
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
|
|
||||||
|
|
||||||
$added = self::$index->getAdded(array('ID'));
|
$added = self::$index->getAdded(array('ID'));
|
||||||
// Some databases don't output $added in a consistent order; that's okay
|
// Some databases don't output $added in a consistent order; that's okay
|
||||||
usort($added, function ($a, $b) {return $a['ID']-$b['ID']; });
|
usort($added, function ($a, $b) {
|
||||||
|
return $a['ID']-$b['ID'];
|
||||||
|
});
|
||||||
|
|
||||||
$this->assertEquals($added, array(
|
$this->assertEquals($added, array(
|
||||||
array('ID' => $container1->ID),
|
array('ID' => $container1->ID),
|
||||||
@ -173,8 +95,11 @@ class SearchUpdaterTest extends SapphireTest
|
|||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$added = self::$index->getAdded(array('ID'));
|
$added = self::$index->getAdded(array('ID'));
|
||||||
|
|
||||||
// Some databases don't output $added in a consistent order; that's okay
|
// Some databases don't output $added in a consistent order; that's okay
|
||||||
usort($added, function ($a, $b) {return $a['ID']-$b['ID']; });
|
usort($added, function ($a, $b) {
|
||||||
|
return $a['ID']-$b['ID'];
|
||||||
|
});
|
||||||
|
|
||||||
$this->assertEquals($added, array(
|
$this->assertEquals($added, array(
|
||||||
array('ID' => $container1->ID),
|
array('ID' => $container1->ID),
|
||||||
|
31
tests/SearchUpdaterTest/SearchUpdaterTest_Container.php
Normal file
31
tests/SearchUpdaterTest/SearchUpdaterTest_Container.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany;
|
||||||
|
|
||||||
|
class SearchUpdaterTest_Container extends DataObject
|
||||||
|
{
|
||||||
|
private static $db = array(
|
||||||
|
'Field1' => 'Varchar',
|
||||||
|
'Field2' => 'Varchar',
|
||||||
|
'MyDate' => 'Date',
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $table_name = 'SearchUpdaterTest_Container';
|
||||||
|
|
||||||
|
private static $has_one = array(
|
||||||
|
'HasOneObject' => SearchUpdaterTest_HasOne::class
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $has_many = array(
|
||||||
|
'HasManyObjects' => SearchUpdaterTest_HasMany::class
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $many_many = array(
|
||||||
|
'ManyManyObjects' => SearchUpdaterTest_ManyMany::class
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to test inherited ambiguous relationships.
|
||||||
|
*/
|
||||||
|
class SearchUpdaterTest_ExtendedContainer extends SearchUpdaterTest_OtherContainer
|
||||||
|
{
|
||||||
|
private static $table_name = 'SearchUpdaterTest_ExtendedContainer';
|
||||||
|
|
||||||
|
private static $db = [
|
||||||
|
'SomeField' => 'Varchar',
|
||||||
|
];
|
||||||
|
}
|
21
tests/SearchUpdaterTest/SearchUpdaterTest_HasMany.php
Normal file
21
tests/SearchUpdaterTest/SearchUpdaterTest_HasMany.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
|
||||||
|
class SearchUpdaterTest_HasMany extends DataObject
|
||||||
|
{
|
||||||
|
private static $db = array(
|
||||||
|
'Field1' => 'Varchar',
|
||||||
|
'Field2' => 'Varchar'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $table_name = 'SearchUpdaterTest_HasMany';
|
||||||
|
|
||||||
|
private static $has_one = array(
|
||||||
|
'HasManyContainer' => SearchUpdaterTest_Container::class,
|
||||||
|
'HasManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
|
||||||
|
);
|
||||||
|
}
|
21
tests/SearchUpdaterTest/SearchUpdaterTest_HasOne.php
Normal file
21
tests/SearchUpdaterTest/SearchUpdaterTest_HasOne.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
|
||||||
|
class SearchUpdaterTest_HasOne extends DataObject
|
||||||
|
{
|
||||||
|
private static $db = array(
|
||||||
|
'Field1' => 'Varchar',
|
||||||
|
'Field2' => 'Varchar'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $table_name = 'SearchUpdaterTest_HasOne';
|
||||||
|
|
||||||
|
private static $has_many = array(
|
||||||
|
'HasManyContainers' => SearchUpdaterTest_Container::class,
|
||||||
|
'HasManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
|
||||||
|
);
|
||||||
|
}
|
17
tests/SearchUpdaterTest/SearchUpdaterTest_Index.php
Normal file
17
tests/SearchUpdaterTest/SearchUpdaterTest_Index.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
|
||||||
|
class SearchUpdaterTest_Index extends SearchIndex_Recording
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchUpdaterTest_Container::class);
|
||||||
|
|
||||||
|
$this->addFilterField('Field1');
|
||||||
|
$this->addFilterField('HasOneObject.Field1');
|
||||||
|
$this->addFilterField('HasManyObjects.Field1');
|
||||||
|
}
|
||||||
|
}
|
20
tests/SearchUpdaterTest/SearchUpdaterTest_ManyMany.php
Normal file
20
tests/SearchUpdaterTest/SearchUpdaterTest_ManyMany.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
|
||||||
|
class SearchUpdaterTest_ManyMany extends DataObject
|
||||||
|
{
|
||||||
|
private static $db = array(
|
||||||
|
'Field1' => 'Varchar',
|
||||||
|
'Field2' => 'Varchar'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $table_name = 'SearchUpdaterTest_ManyMany';
|
||||||
|
|
||||||
|
private static $belongs_many_many = array(
|
||||||
|
'ManyManyContainer' => SearchUpdaterTest_Container::class,
|
||||||
|
'ManyManyOtherContainer' => SearchUpdaterTest_OtherContainer::class,
|
||||||
|
);
|
||||||
|
}
|
21
tests/SearchUpdaterTest/SearchUpdaterTest_OtherContainer.php
Normal file
21
tests/SearchUpdaterTest/SearchUpdaterTest_OtherContainer.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchUpdaterTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to test ambiguous relationships.
|
||||||
|
*/
|
||||||
|
class SearchUpdaterTest_OtherContainer extends DataObject
|
||||||
|
{
|
||||||
|
private static $table_name = 'SearchUpdaterTest_OtherContainer';
|
||||||
|
|
||||||
|
private static $has_many = [
|
||||||
|
'HasManyObjects' => SearchUpdaterTest_HasMany::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $many_many = [
|
||||||
|
'ManyManyObjects' => SearchUpdaterTest_ManyMany::class,
|
||||||
|
];
|
||||||
|
}
|
@ -1,21 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
{
|
|
||||||
// TODO: Currently theres a failure if you addClass a non-table class
|
|
||||||
private static $db = array(
|
|
||||||
'TestText' => 'Varchar'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
{
|
use SilverStripe\Dev\SapphireTest;
|
||||||
public function init()
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
{
|
use SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest\SearchVariantSiteTreeSubsitesPolyhomeTest_Index;
|
||||||
$this->addClass('SearchVariantSiteTreeSubsitesPolyhomeTest_Item');
|
use SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest\SearchVariantSiteTreeSubsitesPolyhomeTest_Item;
|
||||||
$this->addFilterField('TestText');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest
|
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
|
||||||
|
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchVariantSiteTreeSubsitesPolyhomeTest_Item::class);
|
||||||
|
$this->addFilterField('TestText');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest;
|
||||||
|
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
|
||||||
|
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree
|
||||||
|
{
|
||||||
|
private static $table_name = 'SearchVariantSiteTreeSubsitesPolyhomeTest_Item';
|
||||||
|
|
||||||
|
// TODO: Currently theres a failure if you addClass a non-table class
|
||||||
|
private static $db = array(
|
||||||
|
'TestText' => 'Varchar'
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
|
||||||
class SearchVariantSubsiteTest extends SapphireTest
|
class SearchVariantSubsiteTest extends SapphireTest
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -84,8 +88,5 @@ class SearchVariantSubsiteTest extends SapphireTest
|
|||||||
//subsite filter has been modified with our arbitrary test value. The second value is not set
|
//subsite filter has been modified with our arbitrary test value. The second value is not set
|
||||||
//this proves that the query has not been altered by the variant
|
//this proves that the query has not been altered by the variant
|
||||||
$this->assertEquals(2, $query->require['_subsite'][0]);
|
$this->assertEquals(2, $query->require['_subsite'][0]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Index;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_IndexNoStage;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
|
||||||
class SearchVariantVersionedTest extends SapphireTest
|
class SearchVariantVersionedTest extends SapphireTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -7,27 +22,24 @@ class SearchVariantVersionedTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
private static $index = null;
|
private static $index = null;
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected static $extra_dataobjects = array(
|
||||||
'SearchVariantVersionedTest_Item'
|
SearchVariantVersionedTest_Item::class
|
||||||
);
|
);
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
// Check versioned available
|
|
||||||
if (!class_exists('Versioned')) {
|
|
||||||
return $this->markTestSkipped('The versioned decorator is not installed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$index === null) {
|
if (self::$index === null) {
|
||||||
self::$index = singleton('SearchVariantVersionedTest_Index');
|
self::$index = singleton(SearchVariantVersionedTest_Index::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchUpdater::bind_manipulation_capture();
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
|
||||||
Config::inst()->update('Injector', 'SearchUpdateProcessor', array(
|
Config::modify()->set(Injector::class, SearchUpdateProcessor::class, array(
|
||||||
'class' => 'SearchUpdateImmediateProcessor'
|
'class' => SearchUpdateImmediateProcessor::class
|
||||||
));
|
));
|
||||||
|
|
||||||
FullTextSearch::force_index_list(self::$index);
|
FullTextSearch::force_index_list(self::$index);
|
||||||
@ -42,20 +54,21 @@ class SearchVariantVersionedTest extends SapphireTest
|
|||||||
$item->write();
|
$item->write();
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$this->assertEquals(self::$index->getAdded(array('ID', '_versionedstage')), array(
|
$this->assertEquals(array(
|
||||||
array('ID' => $item->ID, '_versionedstage' => 'Stage')
|
array('ID' => $item->ID, '_versionedstage' => 'Stage')
|
||||||
));
|
), self::$index->getAdded(array('ID', '_versionedstage')));
|
||||||
|
|
||||||
// Check that publish updates Live
|
// Check that publish updates Live
|
||||||
|
|
||||||
self::$index->reset();
|
self::$index->reset();
|
||||||
|
|
||||||
$item->publish("Stage", "Live");
|
$item->copyVersionToStage('Stage', 'Live');
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$this->assertEquals(self::$index->getAdded(array('ID', '_versionedstage')), array(
|
$this->assertEquals(array(
|
||||||
|
array('ID' => $item->ID, '_versionedstage' => 'Stage'),
|
||||||
array('ID' => $item->ID, '_versionedstage' => 'Live')
|
array('ID' => $item->ID, '_versionedstage' => 'Live')
|
||||||
));
|
), self::$index->getAdded(array('ID', '_versionedstage')));
|
||||||
|
|
||||||
// Just update a SiteTree field, and check it updates Stage
|
// Just update a SiteTree field, and check it updates Stage
|
||||||
|
|
||||||
@ -83,7 +96,7 @@ class SearchVariantVersionedTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertCount(1, self::$index->deleted);
|
$this->assertCount(1, self::$index->deleted);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'SiteTree',
|
SiteTree::class,
|
||||||
self::$index->deleted[0]['base']
|
self::$index->deleted[0]['base']
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
@ -92,54 +105,29 @@ class SearchVariantVersionedTest extends SapphireTest
|
|||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'Live',
|
'Live',
|
||||||
self::$index->deleted[0]['state']['SearchVariantVersioned']
|
self::$index->deleted[0]['state'][SearchVariantVersioned::class]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExcludeVariantState()
|
public function testExcludeVariantState()
|
||||||
{
|
{
|
||||||
$index = singleton('SearchVariantVersionedTest_IndexNoStage');
|
$index = singleton(SearchVariantVersionedTest_IndexNoStage::class);
|
||||||
FullTextSearch::force_index_list($index);
|
FullTextSearch::force_index_list($index);
|
||||||
|
|
||||||
// Check that write doesn't update stage
|
// Check that write doesn't update stage
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
||||||
$item->write();
|
$item->write();
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$this->assertEquals($index->getAdded(array('ID', '_versionedstage')), array());
|
$this->assertEquals(array(), $index->getAdded(array('ID', '_versionedstage')));
|
||||||
|
|
||||||
// Check that publish updates Live
|
// Check that publish updates Live
|
||||||
$index->reset();
|
$index->reset();
|
||||||
$item->publish("Stage", "Live");
|
|
||||||
|
$item->copyVersionToStage('Stage', 'Live');
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$this->assertEquals($index->getAdded(array('ID', '_versionedstage')), array(
|
$this->assertEquals(array(
|
||||||
array('ID' => $item->ID, '_versionedstage' => 'Live')
|
array('ID' => $item->ID, '_versionedstage' => 'Live')
|
||||||
));
|
), $index->getAdded(array('ID', '_versionedstage')));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchVariantVersionedTest_Item extends SiteTree implements TestOnly
|
|
||||||
{
|
|
||||||
// TODO: Currently theres a failure if you addClass a non-table class
|
|
||||||
private static $db = array(
|
|
||||||
'TestText' => 'Varchar'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchVariantVersionedTest_Index extends SearchIndex_Recording
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchVariantVersionedTest_Item');
|
|
||||||
$this->addFilterField('TestText');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchVariantVersionedTest_IndexNoStage extends SearchIndex_Recording
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchVariantVersionedTest_Item');
|
|
||||||
$this->addFilterField('TestText');
|
|
||||||
$this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
|
||||||
|
class SearchVariantVersionedTest_Index extends SearchIndex_Recording
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchVariantVersionedTest_Item::class);
|
||||||
|
$this->addFilterField('TestText');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
|
|
||||||
|
class SearchVariantVersionedTest_IndexNoStage extends SearchIndex_Recording
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchVariantVersionedTest_Item::class);
|
||||||
|
$this->addFilterField('TestText');
|
||||||
|
$this->excludeVariantState(array(SearchVariantVersioned::class => 'Stage'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest;
|
||||||
|
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
|
class SearchVariantVersionedTest_Item extends SiteTree implements TestOnly
|
||||||
|
{
|
||||||
|
private static $table_name = 'SearchVariantVersionedTest_Item';
|
||||||
|
|
||||||
|
// TODO: Currently theres a failure if you addClass a non-table class
|
||||||
|
private static $db = array(
|
||||||
|
'TestText' => 'Varchar'
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Tests\Solr4ServiceTest\Solr4ServiceTest_RecordingService;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test solr 4.0 compatibility
|
* Test solr 4.0 compatibility
|
||||||
*/
|
*/
|
||||||
@ -16,7 +21,7 @@ class Solr4ServiceTest extends SapphireTest
|
|||||||
|
|
||||||
protected function getMockDocument($id)
|
protected function getMockDocument($id)
|
||||||
{
|
{
|
||||||
$document = new Apache_Solr_Document();
|
$document = new \Apache_Solr_Document();
|
||||||
$document->setField('id', $id);
|
$document->setField('id', $id);
|
||||||
$document->setField('title', "Item $id");
|
$document->setField('title', "Item $id");
|
||||||
return $document;
|
return $document;
|
||||||
@ -58,16 +63,3 @@ class Solr4ServiceTest extends SapphireTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Solr4ServiceTest_RecordingService extends Solr4Service_Core
|
|
||||||
{
|
|
||||||
protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
|
|
||||||
{
|
|
||||||
return $rawPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _sendRawGet($url, $timeout = false)
|
|
||||||
{
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
18
tests/Solr4ServiceTest/Solr4ServiceTest_RecordingService.php
Normal file
18
tests/Solr4ServiceTest/Solr4ServiceTest_RecordingService.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\Solr4ServiceTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service_Core;
|
||||||
|
|
||||||
|
class Solr4ServiceTest_RecordingService extends Solr4Service_Core
|
||||||
|
{
|
||||||
|
protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
|
||||||
|
{
|
||||||
|
return $rawPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _sendRawGet($url, $timeout = false)
|
||||||
|
{
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (class_exists('Phockito')) {
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
Phockito::include_hamcrest(false);
|
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest\SolrIndexSubsitesTest_Index;
|
||||||
|
|
||||||
|
if (class_exists('\Phockito')) {
|
||||||
|
\Phockito::include_hamcrest(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subsite specific solr testing
|
* Subsite specific solr testing
|
||||||
*/
|
*/
|
||||||
class SolrIndexSubsitesTest extends SapphireTest {
|
class SolrIndexSubsitesTest extends SapphireTest
|
||||||
|
{
|
||||||
public static $fixture_file = 'SolrIndexSubsitesTest.yml';
|
// @todo
|
||||||
|
// protected static $fixture_file = 'SolrIndexSubsitesTest/SolrIndexSubsitesTest.yml';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var SolrIndexSubsitesTest_Index
|
* @var SolrIndexSubsitesTest_Index
|
||||||
@ -18,27 +24,21 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
|
|
||||||
protected $server = null;
|
protected $server = null;
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
// Prevent parent::setUp() crashing on db build
|
// Prevent parent::setUp() crashing on db build
|
||||||
if (!class_exists('Subsite')) {
|
if (!class_exists('Subsite')) {
|
||||||
$this->skipTest = true;
|
$this->skipTest = true;
|
||||||
|
$this->markTestSkipped("These tests need the Subsite module installed to run");
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->server = $_SERVER;
|
$this->server = $_SERVER;
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
if (!class_exists('\Phockito')) {
|
||||||
$this->skipTest = true;
|
$this->skipTest = true;
|
||||||
$this->markTestSkipped("These tests need the Phockito module installed to run");
|
$this->markTestSkipped("These tests need the \Phockito module installed to run");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check versioned available
|
|
||||||
if (!class_exists('Subsite')) {
|
|
||||||
$this->skipTest = true;
|
|
||||||
$this->markTestSkipped('The subsite module is not installed');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
|
|
||||||
SearchUpdater::bind_manipulation_capture();
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
|
||||||
Config::inst()->update('Injector', 'SearchUpdateProcessor', array(
|
Config::modify()->set('Injector', 'SearchUpdateProcessor', array(
|
||||||
'class' => 'SearchUpdateImmediateProcessor'
|
'class' => 'SearchUpdateImmediateProcessor'
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
SearchUpdater::clear_dirty_indexes();
|
SearchUpdater::clear_dirty_indexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown()
|
||||||
{
|
{
|
||||||
if ($this->server) {
|
if ($this->server) {
|
||||||
$_SERVER = $this->server;
|
$_SERVER = $this->server;
|
||||||
@ -67,7 +67,7 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
|
|
||||||
protected function getServiceMock()
|
protected function getServiceMock()
|
||||||
{
|
{
|
||||||
return Phockito::mock('Solr4Service');
|
return \Phockito::mock('Solr4Service');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,11 +79,11 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
protected function getExpectedDocumentId($object, $subsiteID, $stage = null)
|
protected function getExpectedDocumentId($object, $subsiteID, $stage = null)
|
||||||
{
|
{
|
||||||
$id = $object->ID;
|
$id = $object->ID;
|
||||||
$class = ClassInfo::baseDataClass($object);
|
$class = DataObject::getSchema()->baseDataClass($object);
|
||||||
$variants = array();
|
$variants = array();
|
||||||
|
|
||||||
// Check subsite
|
// Check subsite
|
||||||
if(class_exists('Subsite') && $object->hasOne('Subsite')) {
|
if (class_exists('Subsite') && DataObject::getSchema()->hasOneComponent($object->getClassName(), 'Subsite')) {
|
||||||
$variants[] = '"SearchVariantSubsites":"' . $subsiteID. '"';
|
$variants[] = '"SearchVariantSubsites":"' . $subsiteID. '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
// Add records to first subsite
|
// Add records to first subsite
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::reading_stage('Stage');
|
||||||
$_SERVER['HTTP_HOST'] = 'www.subsite1.com';
|
$_SERVER['HTTP_HOST'] = 'www.subsite1.com';
|
||||||
Phockito::reset($serviceMock);
|
\Phockito::reset($serviceMock);
|
||||||
$file = new File();
|
$file = new File();
|
||||||
$file->Title = 'My File';
|
$file->Title = 'My File';
|
||||||
$file->SubsiteID = $subsite1->ID;
|
$file->SubsiteID = $subsite1->ID;
|
||||||
@ -128,12 +128,12 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
'File_Title' => 'My File',
|
'File_Title' => 'My File',
|
||||||
'_subsite' => $subsite1->ID
|
'_subsite' => $subsite1->ID
|
||||||
));
|
));
|
||||||
Phockito::verify($serviceMock)->addDocument($doc1);
|
\Phockito::verify($serviceMock)->addDocument($doc1);
|
||||||
Phockito::verify($serviceMock)->addDocument($doc2);
|
\Phockito::verify($serviceMock)->addDocument($doc2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCorrectSubsiteIDOnPageWrite() {
|
public function testCorrectSubsiteIDOnPageWrite()
|
||||||
|
{
|
||||||
$mockWrites = array(
|
$mockWrites = array(
|
||||||
'3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}' => array(
|
'3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}' => array(
|
||||||
'base' => 'SiteTree',
|
'base' => 'SiteTree',
|
||||||
@ -190,7 +190,8 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCorrectSubsiteIDOnFileWrite() {
|
public function testCorrectSubsiteIDOnFileWrite()
|
||||||
|
{
|
||||||
$subsiteIDs = array('0') + $this->allFixtureIDs('Subsite');
|
$subsiteIDs = array('0') + $this->allFixtureIDs('Subsite');
|
||||||
$mockWrites = array(
|
$mockWrites = array(
|
||||||
'35910:File:a:0:{}' => array(
|
'35910:File:a:0:{}' => array(
|
||||||
@ -241,15 +242,4 @@ class SolrIndexSubsitesTest extends SapphireTest {
|
|||||||
$this->assertEquals($subsite->ID, $statefulIDs['state']['SearchVariantSubsites']);
|
$this->assertEquals($subsite->ID, $statefulIDs['state']['SearchVariantSubsites']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrIndexSubsitesTest_Index extends SolrIndex
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('File');
|
|
||||||
$this->addClass('SiteTree');
|
|
||||||
$this->addAllFulltextFields();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
17
tests/SolrIndexSubsitesTest/SolrIndexSubsitesTest_Index.php
Normal file
17
tests/SolrIndexSubsitesTest/SolrIndexSubsitesTest_Index.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\Assets\File;
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
|
||||||
|
class SolrIndexSubsitesTest_Index extends SolrIndex
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(File::class);
|
||||||
|
$this->addClass(SiteTree::class);
|
||||||
|
$this->addAllFulltextFields();
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +1,58 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Core\Kernel;
|
||||||
|
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_FakeIndex2;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_OtherContainer;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
|
||||||
|
|
||||||
class SolrIndexTest extends SapphireTest
|
class SolrIndexTest extends SapphireTest
|
||||||
{
|
{
|
||||||
public function setUpOnce()
|
|
||||||
{
|
|
||||||
parent::setUpOnce();
|
|
||||||
|
|
||||||
if (class_exists('Phockito')) {
|
|
||||||
Phockito::include_hamcrest(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
|
||||||
$this->markTestSkipped("These tests need the Phockito module installed to run");
|
|
||||||
$this->skipTest = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFieldDataHasOne()
|
public function testFieldDataHasOne()
|
||||||
{
|
{
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$data = $index->fieldData('HasOneObject.Field1');
|
$data = $index->fieldData('HasOneObject.Field1');
|
||||||
$data = $data['SearchUpdaterTest_Container_HasOneObject_Field1'];
|
|
||||||
|
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['origin']);
|
$data = $data[SearchUpdaterTest_Container::class . '_HasOneObject_Field1'];
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['base']);
|
|
||||||
$this->assertEquals('SearchUpdaterTest_HasOne', $data['class']);
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['origin']);
|
||||||
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['base']);
|
||||||
|
$this->assertEquals(SearchUpdaterTest_HasOne::class, $data['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFieldDataHasMany()
|
public function testFieldDataHasMany()
|
||||||
{
|
{
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$data = $index->fieldData('HasManyObjects.Field1');
|
$data = $index->fieldData('HasManyObjects.Field1');
|
||||||
$data = $data['SearchUpdaterTest_Container_HasManyObjects_Field1'];
|
$data = $data[SearchUpdaterTest_Container::class . '_HasManyObjects_Field1'];
|
||||||
|
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['origin']);
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['origin']);
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['base']);
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['base']);
|
||||||
$this->assertEquals('SearchUpdaterTest_HasMany', $data['class']);
|
$this->assertEquals(SearchUpdaterTest_HasMany::class, $data['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFieldDataManyMany()
|
public function testFieldDataManyMany()
|
||||||
{
|
{
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$data = $index->fieldData('ManyManyObjects.Field1');
|
$data = $index->fieldData('ManyManyObjects.Field1');
|
||||||
$data = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
|
$data = $data[SearchUpdaterTest_Container::class . '_ManyManyObjects_Field1'];
|
||||||
|
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['origin']);
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['origin']);
|
||||||
$this->assertEquals('SearchUpdaterTest_Container', $data['base']);
|
$this->assertEquals(SearchUpdaterTest_Container::class, $data['base']);
|
||||||
$this->assertEquals('SearchUpdaterTest_ManyMany', $data['class']);
|
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $data['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFieldDataAmbiguousHasMany()
|
public function testFieldDataAmbiguousHasMany()
|
||||||
@ -58,18 +60,18 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index = new SolrIndexTest_AmbiguousRelationIndex();
|
$index = new SolrIndexTest_AmbiguousRelationIndex();
|
||||||
$data = $index->fieldData('HasManyObjects.Field1');
|
$data = $index->fieldData('HasManyObjects.Field1');
|
||||||
|
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_Container_HasManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_Container::class . '_HasManyObjects_Field1', $data);
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_HasManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_OtherContainer::class . '_HasManyObjects_Field1', $data);
|
||||||
|
|
||||||
$dataContainer = $data['SearchUpdaterTest_Container_HasManyObjects_Field1'];
|
$dataContainer = $data[SearchUpdaterTest_Container::class . '_HasManyObjects_Field1'];
|
||||||
$this->assertEquals($dataContainer['origin'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
|
||||||
$this->assertEquals($dataContainer['base'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
|
||||||
$this->assertEquals($dataContainer['class'], 'SearchUpdaterTest_HasMany');
|
$this->assertEquals(SearchUpdaterTest_HasMany::class, $dataContainer['class']);
|
||||||
|
|
||||||
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_HasManyObjects_Field1'];
|
$dataOtherContainer = $data[SearchUpdaterTest_OtherContainer::class . '_HasManyObjects_Field1'];
|
||||||
$this->assertEquals($dataOtherContainer['origin'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
|
||||||
$this->assertEquals($dataOtherContainer['base'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
|
||||||
$this->assertEquals($dataOtherContainer['class'], 'SearchUpdaterTest_HasMany');
|
$this->assertEquals(SearchUpdaterTest_HasMany::class, $dataOtherContainer['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFieldDataAmbiguousManyMany()
|
public function testFieldDataAmbiguousManyMany()
|
||||||
@ -77,18 +79,18 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index = new SolrIndexTest_AmbiguousRelationIndex();
|
$index = new SolrIndexTest_AmbiguousRelationIndex();
|
||||||
$data = $index->fieldData('ManyManyObjects.Field1');
|
$data = $index->fieldData('ManyManyObjects.Field1');
|
||||||
|
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_Container_ManyManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_Container::class . '_ManyManyObjects_Field1', $data);
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_OtherContainer::class . '_ManyManyObjects_Field1', $data);
|
||||||
|
|
||||||
$dataContainer = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
|
$dataContainer = $data[SearchUpdaterTest_Container::class . '_ManyManyObjects_Field1'];
|
||||||
$this->assertEquals($dataContainer['origin'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
|
||||||
$this->assertEquals($dataContainer['base'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
|
||||||
$this->assertEquals($dataContainer['class'], 'SearchUpdaterTest_ManyMany');
|
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataContainer['class']);
|
||||||
|
|
||||||
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1'];
|
$dataOtherContainer = $data[SearchUpdaterTest_OtherContainer::class . '_ManyManyObjects_Field1'];
|
||||||
$this->assertEquals($dataOtherContainer['origin'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
|
||||||
$this->assertEquals($dataOtherContainer['base'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
|
||||||
$this->assertEquals($dataOtherContainer['class'], 'SearchUpdaterTest_ManyMany');
|
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataOtherContainer['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFieldDataAmbiguousManyManyInherited()
|
public function testFieldDataAmbiguousManyManyInherited()
|
||||||
@ -96,19 +98,19 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index = new SolrIndexTest_AmbiguousRelationInheritedIndex();
|
$index = new SolrIndexTest_AmbiguousRelationInheritedIndex();
|
||||||
$data = $index->fieldData('ManyManyObjects.Field1');
|
$data = $index->fieldData('ManyManyObjects.Field1');
|
||||||
|
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_Container_ManyManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_Container::class . '_ManyManyObjects_Field1', $data);
|
||||||
$this->assertArrayHasKey('SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1', $data);
|
$this->assertArrayHasKey(SearchUpdaterTest_OtherContainer::class . '_ManyManyObjects_Field1', $data);
|
||||||
$this->assertArrayNotHasKey('SearchUpdaterTest_ExtendedContainer_ManyManyObjects_Field1', $data);
|
$this->assertArrayNotHasKey(SearchUpdaterTest_ExtendedContainer::class . '_ManyManyObjects_Field1', $data);
|
||||||
|
|
||||||
$dataContainer = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
|
$dataContainer = $data[SearchUpdaterTest_Container::class . '_ManyManyObjects_Field1'];
|
||||||
$this->assertEquals($dataContainer['origin'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['origin']);
|
||||||
$this->assertEquals($dataContainer['base'], 'SearchUpdaterTest_Container');
|
$this->assertEquals(SearchUpdaterTest_Container::class, $dataContainer['base']);
|
||||||
$this->assertEquals($dataContainer['class'], 'SearchUpdaterTest_ManyMany');
|
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataContainer['class']);
|
||||||
|
|
||||||
$dataOtherContainer = $data['SearchUpdaterTest_OtherContainer_ManyManyObjects_Field1'];
|
$dataOtherContainer = $data[SearchUpdaterTest_OtherContainer::class . '_ManyManyObjects_Field1'];
|
||||||
$this->assertEquals($dataOtherContainer['origin'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['origin']);
|
||||||
$this->assertEquals($dataOtherContainer['base'], 'SearchUpdaterTest_OtherContainer');
|
$this->assertEquals(SearchUpdaterTest_OtherContainer::class, $dataOtherContainer['base']);
|
||||||
$this->assertEquals($dataOtherContainer['class'], 'SearchUpdaterTest_ManyMany');
|
$this->assertEquals(SearchUpdaterTest_ManyMany::class, $dataOtherContainer['class']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,15 +118,20 @@ class SolrIndexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testBoostedQuery()
|
public function testBoostedQuery()
|
||||||
{
|
{
|
||||||
$serviceMock = $this->getServiceMock();
|
/** @var Solr3Service|PHPUnit_Framework_MockObject_MockObject $serviceMock */
|
||||||
Phockito::when($serviceMock)
|
$serviceMock = $this->getMockBuilder(Solr3Service::class)
|
||||||
->search(
|
->setMethods(['search'])
|
||||||
\Hamcrest_Matchers::anything(),
|
->getMock();
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
$serviceMock->expects($this->once())
|
||||||
\Hamcrest_Matchers::anything(),
|
->method('search')
|
||||||
\Hamcrest_Matchers::anything()
|
->with(
|
||||||
)->return($this->getFakeRawSolrResponse());
|
$this->equalTo('+(Field1:term^1.5 OR HasOneObject_Field1:term^3)'),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
)->willReturn($this->getFakeRawSolrResponse());
|
||||||
|
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$index->setService($serviceMock);
|
$index->setService($serviceMock);
|
||||||
@ -136,15 +143,6 @@ class SolrIndexTest extends SapphireTest
|
|||||||
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
||||||
);
|
);
|
||||||
$index->search($query);
|
$index->search($query);
|
||||||
|
|
||||||
Phockito::verify($serviceMock)
|
|
||||||
->search(
|
|
||||||
'+(Field1:term^1.5 OR HasOneObject_Field1:term^3)',
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,15 +150,21 @@ class SolrIndexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testBoostedField()
|
public function testBoostedField()
|
||||||
{
|
{
|
||||||
$serviceMock = $this->getServiceMock();
|
/** @var Solr3Service|PHPUnit_Framework_MockObject_MockObject $serviceMock */
|
||||||
Phockito::when($serviceMock)
|
$serviceMock = $this->getMockBuilder(Solr3Service::class)
|
||||||
->search(
|
->setMethods(['search'])
|
||||||
\Hamcrest_Matchers::anything(),
|
->getMock();
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
$serviceMock->expects($this->once())
|
||||||
\Hamcrest_Matchers::anything(),
|
->method('search')
|
||||||
\Hamcrest_Matchers::anything()
|
->with(
|
||||||
)->return($this->getFakeRawSolrResponse());
|
$this->equalTo('+term'),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->equalTo(['qf' => SearchUpdaterTest_Container::class . '_Field1^1.5 ' . SearchUpdaterTest_Container::class . '_Field2^2.1 _text',
|
||||||
|
'fq' => '+(_versionedstage:"" (*:* -_versionedstage:[* TO *]))']),
|
||||||
|
$this->anything()
|
||||||
|
)->willReturn($this->getFakeRawSolrResponse());
|
||||||
|
|
||||||
$index = new SolrIndexTest_BoostedIndex();
|
$index = new SolrIndexTest_BoostedIndex();
|
||||||
$index->setService($serviceMock);
|
$index->setService($serviceMock);
|
||||||
@ -168,32 +172,35 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$query = new SearchQuery();
|
$query = new SearchQuery();
|
||||||
$query->search('term');
|
$query->search('term');
|
||||||
$index->search($query);
|
$index->search($query);
|
||||||
|
|
||||||
// Ensure matcher contains correct boost in 'qf' parameter
|
|
||||||
$matcher = new Hamcrest_Array_IsArrayContainingKeyValuePair(
|
|
||||||
new Hamcrest_Core_IsEqual('qf'),
|
|
||||||
new Hamcrest_Core_IsEqual('SearchUpdaterTest_Container_Field1^1.5 SearchUpdaterTest_Container_Field2^2.1 _text')
|
|
||||||
);
|
|
||||||
Phockito::verify($serviceMock)
|
|
||||||
->search(
|
|
||||||
'+term',
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
$matcher,
|
|
||||||
\Hamcrest_Matchers::anything()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHighlightQueryOnBoost()
|
public function testHighlightQueryOnBoost()
|
||||||
{
|
{
|
||||||
$serviceMock = $this->getServiceMock();
|
/** @var SilverStripe\FullTextSearch\Solr\Services\Solr3Service|ObjectProphecy $serviceMock */
|
||||||
Phockito::when($serviceMock)->search(
|
$serviceMock = $this->getMockBuilder(Solr3Service::class)
|
||||||
\Hamcrest_Matchers::anything(),
|
->setMethods(['search'])
|
||||||
\Hamcrest_Matchers::anything(),
|
->getMock();
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
$serviceMock->expects($this->exactly(2))
|
||||||
\Hamcrest_Matchers::anything()
|
->method('search')
|
||||||
)->return($this->getFakeRawSolrResponse());
|
->withConsecutive(
|
||||||
|
[
|
||||||
|
$this->equalTo('+(Field1:term^1.5 OR HasOneObject_Field1:term^3)'),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->logicalNot(
|
||||||
|
$this->arrayHasKey('hl.q')
|
||||||
|
),
|
||||||
|
$this->anything()
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo('+(Field1:term^1.5 OR HasOneObject_Field1:term^3)'),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->arrayHasKey('hl.q'),
|
||||||
|
$this->anything()
|
||||||
|
]
|
||||||
|
)->willReturn($this->getFakeRawSolrResponse());
|
||||||
|
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$index->setService($serviceMock);
|
$index->setService($serviceMock);
|
||||||
@ -206,14 +213,6 @@ class SolrIndexTest extends SapphireTest
|
|||||||
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
||||||
);
|
);
|
||||||
$index->search($query);
|
$index->search($query);
|
||||||
Phockito::verify(
|
|
||||||
$serviceMock)->search(
|
|
||||||
'+(Field1:term^1.5 OR HasOneObject_Field1:term^3)',
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::not(\Hamcrest_Matchers::hasKeyInArray('hl.q')),
|
|
||||||
\Hamcrest_Matchers::anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Search with highlighting
|
// Search with highlighting
|
||||||
$query = new SearchQuery();
|
$query = new SearchQuery();
|
||||||
@ -223,19 +222,12 @@ class SolrIndexTest extends SapphireTest
|
|||||||
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
||||||
);
|
);
|
||||||
$index->search($query, -1, -1, array('hl' => true));
|
$index->search($query, -1, -1, array('hl' => true));
|
||||||
Phockito::verify(
|
|
||||||
$serviceMock)->search(
|
|
||||||
'+(Field1:term^1.5 OR HasOneObject_Field1:term^3)',
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::anything(),
|
|
||||||
\Hamcrest_Matchers::hasKeyInArray('hl.q'),
|
|
||||||
\Hamcrest_Matchers::anything()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexExcludesNullValues()
|
public function testIndexExcludesNullValues()
|
||||||
{
|
{
|
||||||
$serviceMock = $this->getServiceMock();
|
/** @var Solr3Service|ObjectProphecy $serviceMock */
|
||||||
|
$serviceMock = $this->createMock(Solr3Service::class);
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
$index->setService($serviceMock);
|
$index->setService($serviceMock);
|
||||||
$obj = new SearchUpdaterTest_Container();
|
$obj = new SearchUpdaterTest_Container();
|
||||||
@ -244,36 +236,33 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$obj->Field2 = null;
|
$obj->Field2 = null;
|
||||||
$obj->MyDate = null;
|
$obj->MyDate = null;
|
||||||
$docs = $index->add($obj);
|
$docs = $index->add($obj);
|
||||||
$value = $docs[0]->getField('SearchUpdaterTest_Container_Field1');
|
$value = $docs[0]->getField(SearchUpdaterTest_Container::class . '_Field1');
|
||||||
$this->assertEquals('Field1 val', $value['value'], 'Writes non-NULL string fields');
|
$this->assertEquals('Field1 val', $value['value'], 'Writes non-NULL string fields');
|
||||||
$value = $docs[0]->getField('SearchUpdaterTest_Container_Field2');
|
$value = $docs[0]->getField(SearchUpdaterTest_Container::class . '_Field2');
|
||||||
$this->assertFalse($value, 'Ignores string fields if they are NULL');
|
$this->assertFalse($value, 'Ignores string fields if they are NULL');
|
||||||
$value = $docs[0]->getField('SearchUpdaterTest_Container_MyDate');
|
$value = $docs[0]->getField(SearchUpdaterTest_Container::class . '_MyDate');
|
||||||
$this->assertFalse($value, 'Ignores date fields if they are NULL');
|
$this->assertFalse($value, 'Ignores date fields if they are NULL');
|
||||||
|
|
||||||
$obj->MyDate = '2010-12-30';
|
$obj->MyDate = '2010-12-30';
|
||||||
$docs = $index->add($obj);
|
$docs = $index->add($obj);
|
||||||
$value = $docs[0]->getField('SearchUpdaterTest_Container_MyDate');
|
$value = $docs[0]->getField(SearchUpdaterTest_Container::class . '_MyDate');
|
||||||
$this->assertEquals('2010-12-30T00:00:00Z', $value['value'], 'Writes non-NULL dates');
|
$this->assertEquals('2010-12-30T00:00:00Z', $value['value'], 'Writes non-NULL dates');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddFieldExtraOptions()
|
public function testAddFieldExtraOptions()
|
||||||
{
|
{
|
||||||
Config::inst()->nest();
|
Injector::inst()->get(Kernel::class)->setEnvironment('live');
|
||||||
Config::inst()->update('Director', 'environment_type', 'live'); // dev mode sets stored=true for everything
|
|
||||||
|
|
||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
|
|
||||||
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
||||||
$defField1 = $defs->xpath('field[@name="SearchUpdaterTest_Container_Field1"]');
|
$defField1 = $defs->xpath('field[@name="' . SearchUpdaterTest_Container::class . '_Field1"]');
|
||||||
$this->assertEquals((string)$defField1[0]['stored'], 'false');
|
$this->assertEquals((string)$defField1[0]['stored'], 'false');
|
||||||
|
|
||||||
$index->addFilterField('Field1', null, array('stored' => 'true'));
|
$index->addFilterField('Field1', null, array('stored' => 'true'));
|
||||||
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
||||||
$defField1 = $defs->xpath('field[@name="SearchUpdaterTest_Container_Field1"]');
|
$defField1 = $defs->xpath('field[@name="' . SearchUpdaterTest_Container::class . '_Field1"]');
|
||||||
$this->assertEquals((string)$defField1[0]['stored'], 'true');
|
$this->assertEquals((string)$defField1[0]['stored'], 'true');
|
||||||
|
|
||||||
Config::inst()->unnest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAnalyzer()
|
public function testAddAnalyzer()
|
||||||
@ -281,13 +270,13 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index = new SolrIndexTest_FakeIndex();
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
|
|
||||||
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
||||||
$defField1 = $defs->xpath('field[@name="SearchUpdaterTest_Container_Field1"]');
|
$defField1 = $defs->xpath('field[@name="' . SearchUpdaterTest_Container::class . '_Field1"]');
|
||||||
$analyzers = $defField1[0]->analyzer;
|
$analyzers = $defField1[0]->analyzer;
|
||||||
$this->assertFalse((bool)$analyzers);
|
$this->assertFalse((bool)$analyzers);
|
||||||
|
|
||||||
$index->addAnalyzer('Field1', 'charFilter', array('class' => 'solr.HTMLStripCharFilterFactory'));
|
$index->addAnalyzer('Field1', 'charFilter', array('class' => 'solr.HTMLStripCharFilterFactory'));
|
||||||
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
||||||
$defField1 = $defs->xpath('field[@name="SearchUpdaterTest_Container_Field1"]');
|
$defField1 = $defs->xpath('field[@name="' . SearchUpdaterTest_Container::class . '_Field1"]');
|
||||||
$analyzers = $defField1[0]->analyzer;
|
$analyzers = $defField1[0]->analyzer;
|
||||||
$this->assertTrue((bool)$analyzers);
|
$this->assertTrue((bool)$analyzers);
|
||||||
$this->assertEquals('solr.HTMLStripCharFilterFactory', $analyzers[0]->charFilter[0]['class']);
|
$this->assertEquals('solr.HTMLStripCharFilterFactory', $analyzers[0]->charFilter[0]['class']);
|
||||||
@ -316,11 +305,11 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index->addFulltextField('Field2');
|
$index->addFulltextField('Field2');
|
||||||
$schema = $index->getFieldDefinitions();
|
$schema = $index->getFieldDefinitions();
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
"<field name='SearchUpdaterTest_Container_Field1' type='text' indexed='true' stored='true'",
|
"<field name='" . SearchUpdaterTest_Container::class . "_Field1' type='text' indexed='true' stored='true'",
|
||||||
$schema
|
$schema
|
||||||
);
|
);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
"<field name='SearchUpdaterTest_Container_Field2' type='text' indexed='true' stored='false'",
|
"<field name='" . SearchUpdaterTest_Container::class . "_Field2' type='text' indexed='true' stored='false'",
|
||||||
$schema
|
$schema
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -330,35 +319,19 @@ class SolrIndexTest extends SapphireTest
|
|||||||
$index2->addStoredField('Field2');
|
$index2->addStoredField('Field2');
|
||||||
$schema2 = $index2->getFieldDefinitions();
|
$schema2 = $index2->getFieldDefinitions();
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
"<field name='SearchUpdaterTest_Container_Field1' type='text' indexed='true' stored='false'",
|
"<field name='" . SearchUpdaterTest_Container::class . "_Field1' type='text' indexed='true' stored='false'",
|
||||||
$schema2
|
$schema2
|
||||||
);
|
);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
"<field name='SearchUpdaterTest_Container_Field2' type='text' indexed='true' stored='true'",
|
"<field name='" . SearchUpdaterTest_Container::class . "_Field2' type='text' indexed='true' stored='true'",
|
||||||
$schema2
|
$schema2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Solr3Service
|
|
||||||
*/
|
|
||||||
protected function getServiceMock()
|
|
||||||
{
|
|
||||||
return Phockito::mock('Solr3Service');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getServiceSpy()
|
|
||||||
{
|
|
||||||
$serviceSpy = Phockito::spy('Solr3Service');
|
|
||||||
Phockito::when($serviceSpy)->_sendRawPost()->return($this->getFakeRawSolrResponse());
|
|
||||||
|
|
||||||
return $serviceSpy;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getFakeRawSolrResponse()
|
protected function getFakeRawSolrResponse()
|
||||||
{
|
{
|
||||||
return new Apache_Solr_Response(
|
return new \Apache_Solr_Response(
|
||||||
new Apache_Solr_HttpTransport_Response(
|
new \Apache_Solr_HttpTransport_Response(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
'{}'
|
'{}'
|
||||||
@ -366,94 +339,3 @@ class SolrIndexTest extends SapphireTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SolrIndexTest_FakeIndex extends SolrIndex
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
|
|
||||||
$this->addFilterField('Field1');
|
|
||||||
$this->addFilterField('MyDate', 'Date');
|
|
||||||
$this->addFilterField('HasOneObject.Field1');
|
|
||||||
$this->addFilterField('HasManyObjects.Field1');
|
|
||||||
$this->addFilterField('ManyManyObjects.Field1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SolrIndexTest_FakeIndex2 extends SolrIndex
|
|
||||||
{
|
|
||||||
protected function getStoredDefault()
|
|
||||||
{
|
|
||||||
// Override isDev defaulting to stored
|
|
||||||
return 'false';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
$this->addFilterField('MyDate', 'Date');
|
|
||||||
$this->addFilterField('HasOneObject.Field1');
|
|
||||||
$this->addFilterField('HasManyObjects.Field1');
|
|
||||||
$this->addFilterField('ManyManyObjects.Field1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SolrIndexTest_BoostedIndex extends SolrIndex
|
|
||||||
{
|
|
||||||
protected function getStoredDefault()
|
|
||||||
{
|
|
||||||
// Override isDev defaulting to stored
|
|
||||||
return 'false';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
$this->addAllFulltextFields();
|
|
||||||
$this->setFieldBoosting('SearchUpdaterTest_Container_Field1', 1.5);
|
|
||||||
$this->addBoostedField('Field2', null, array(), 2.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrIndexTest_AmbiguousRelationIndex extends SolrIndex
|
|
||||||
{
|
|
||||||
protected function getStoredDefault()
|
|
||||||
{
|
|
||||||
// Override isDev defaulting to stored
|
|
||||||
return 'false';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
$this->addClass('SearchUpdaterTest_OtherContainer');
|
|
||||||
|
|
||||||
// These relationships exist on both classes
|
|
||||||
$this->addFilterField('HasManyObjects.Field1');
|
|
||||||
$this->addFilterField('ManyManyObjects.Field1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrIndexTest_AmbiguousRelationInheritedIndex extends SolrIndex
|
|
||||||
{
|
|
||||||
protected function getStoredDefault()
|
|
||||||
{
|
|
||||||
// Override isDev defaulting to stored
|
|
||||||
return 'false';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchUpdaterTest_Container');
|
|
||||||
// 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');
|
|
||||||
|
|
||||||
// These relationships exist on both classes
|
|
||||||
$this->addFilterField('HasManyObjects.Field1');
|
|
||||||
$this->addFilterField('ManyManyObjects.Field1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
26
tests/SolrIndexTest/SolrIndexTest_AmbiguousRelationIndex.php
Normal file
26
tests/SolrIndexTest/SolrIndexTest_AmbiguousRelationIndex.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
23
tests/SolrIndexTest/SolrIndexTest_BoostedIndex.php
Normal file
23
tests/SolrIndexTest/SolrIndexTest_BoostedIndex.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
|
||||||
|
class SolrIndexTest_BoostedIndex extends SolrIndex
|
||||||
|
{
|
||||||
|
protected function getStoredDefault()
|
||||||
|
{
|
||||||
|
// Override isDev defaulting to stored
|
||||||
|
return 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchUpdaterTest_Container::class);
|
||||||
|
$this->addAllFulltextFields();
|
||||||
|
$this->setFieldBoosting(SearchUpdaterTest_Container::class . '_Field1', 1.5);
|
||||||
|
$this->addBoostedField('Field2', null, array(), 2.1);
|
||||||
|
}
|
||||||
|
}
|
20
tests/SolrIndexTest/SolrIndexTest_FakeIndex.php
Normal file
20
tests/SolrIndexTest/SolrIndexTest_FakeIndex.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
|
||||||
|
class SolrIndexTest_FakeIndex extends SolrIndex
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchUpdaterTest_Container::class);
|
||||||
|
|
||||||
|
$this->addFilterField('Field1');
|
||||||
|
$this->addFilterField('MyDate', 'Date');
|
||||||
|
$this->addFilterField('HasOneObject.Field1');
|
||||||
|
$this->addFilterField('HasManyObjects.Field1');
|
||||||
|
$this->addFilterField('ManyManyObjects.Field1');
|
||||||
|
}
|
||||||
|
}
|
24
tests/SolrIndexTest/SolrIndexTest_FakeIndex2.php
Normal file
24
tests/SolrIndexTest/SolrIndexTest_FakeIndex2.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
|
|
||||||
|
class SolrIndexTest_FakeIndex2 extends SolrIndex
|
||||||
|
{
|
||||||
|
protected function getStoredDefault()
|
||||||
|
{
|
||||||
|
// Override isDev defaulting to stored
|
||||||
|
return 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchUpdaterTest_Container::class);
|
||||||
|
$this->addFilterField('MyDate', 'Date');
|
||||||
|
$this->addFilterField('HasOneObject.Field1');
|
||||||
|
$this->addFilterField('HasManyObjects.Field1');
|
||||||
|
$this->addFilterField('ManyManyObjects.Field1');
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (class_exists('Phockito')) {
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
Phockito::include_hamcrest(false);
|
|
||||||
}
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest\SolrIndexVersionedTest_Object;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest\SolrVersionedTest_Index;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
class SolrIndexVersionedTest extends SapphireTest
|
class SolrIndexVersionedTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -10,43 +24,32 @@ class SolrIndexVersionedTest extends SapphireTest
|
|||||||
|
|
||||||
protected static $index = null;
|
protected static $index = null;
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected static $extra_dataobjects = array(
|
||||||
'SearchVariantVersionedTest_Item',
|
SearchVariantVersionedTest_Item::class,
|
||||||
'SolrIndexVersionedTest_Object',
|
SolrIndexVersionedTest_Object::class
|
||||||
);
|
);
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
|
||||||
$this->skipTest = true;
|
|
||||||
$this->markTestSkipped("These tests need the Phockito module installed to run");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check versioned available
|
|
||||||
if (!class_exists('Versioned')) {
|
|
||||||
$this->skipTest = true;
|
|
||||||
$this->markTestSkipped('The versioned decorator is not installed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$index === null) {
|
if (self::$index === null) {
|
||||||
self::$index = singleton('SolrVersionedTest_Index');
|
self::$index = singleton(SolrVersionedTest_Index::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchUpdater::bind_manipulation_capture();
|
SearchUpdater::bind_manipulation_capture();
|
||||||
|
|
||||||
Config::inst()->update('Injector', 'SearchUpdateProcessor', array(
|
Config::modify()->set(Injector::class, SearchUpdateProcessor::class, array(
|
||||||
'class' => 'SearchUpdateImmediateProcessor'
|
'class' => SearchUpdateImmediateProcessor::class
|
||||||
));
|
));
|
||||||
|
|
||||||
FullTextSearch::force_index_list(self::$index);
|
FullTextSearch::force_index_list(self::$index);
|
||||||
SearchUpdater::clear_dirty_indexes();
|
SearchUpdater::clear_dirty_indexes();
|
||||||
|
|
||||||
$this->oldMode = Versioned::get_reading_mode();
|
$this->oldMode = Versioned::get_reading_mode();
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::set_stage('Stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
@ -55,9 +58,17 @@ class SolrIndexVersionedTest extends SapphireTest
|
|||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getServiceMock()
|
protected function getServiceMock($setMethods = array())
|
||||||
{
|
{
|
||||||
return Phockito::mock('Solr3Service');
|
// Setup mock
|
||||||
|
/** @var SilverStripe\FullTextSearch\Solr\Services\Solr3Service|ObjectProphecy $serviceMock */
|
||||||
|
$serviceMock = $this->getMockBuilder(Solr3Service::class)
|
||||||
|
->setMethods($setMethods)
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
self::$index->setService($serviceMock);
|
||||||
|
|
||||||
|
return $serviceMock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,166 +79,164 @@ class SolrIndexVersionedTest extends SapphireTest
|
|||||||
protected function getExpectedDocumentId($object, $stage)
|
protected function getExpectedDocumentId($object, $stage)
|
||||||
{
|
{
|
||||||
$id = $object->ID;
|
$id = $object->ID;
|
||||||
$class = ClassInfo::baseDataClass($object);
|
$class = DataObject::getSchema()->baseDataClass($object);
|
||||||
// Prevent subsites from breaking tests
|
// Prevent subsites from breaking tests
|
||||||
|
// TODO: Subsites currently isn't migrated. This needs to be fixed when subsites is fixed.
|
||||||
$subsites = '';
|
$subsites = '';
|
||||||
if(class_exists('Subsite') && $object->hasOne('Subsite')) {
|
if (class_exists('Subsite') && DataObject::getSchema()->hasOneComponent($object->getClassName(), 'Subsite')) {
|
||||||
$subsites = '"SearchVariantSubsites":"0",';
|
$subsites = '"SearchVariantSubsites":"0",';
|
||||||
}
|
}
|
||||||
return $id.'-'.$class.'-{'.$subsites.'"SearchVariantVersioned":"'.$stage.'"}';
|
return $id.'-'.$class.'-{'.$subsites. json_encode(SearchVariantVersioned::class) . ':"'.$stage.'"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $class
|
||||||
|
* @param DataObject $object Item being added
|
||||||
|
* @param string $value Value for class
|
||||||
|
* @param string $stage Stage updated
|
||||||
|
* @return Apache_Solr_Document
|
||||||
|
*/
|
||||||
|
protected function getSolrDocument($class, $object, $value, $stage)
|
||||||
|
{
|
||||||
|
$doc = new \Apache_Solr_Document();
|
||||||
|
$doc->setField('_documentid', $this->getExpectedDocumentId($object, $stage));
|
||||||
|
$doc->setField('ClassName', $class);
|
||||||
|
$doc->setField(DataObject::getSchema()->baseDataClass($class) . '_TestText', $value);
|
||||||
|
$doc->setField('_versionedstage', $stage);
|
||||||
|
$doc->setField('ID', $object->ID);
|
||||||
|
$doc->setField('ClassHierarchy', SearchIntrospection::hierarchy($class));
|
||||||
|
$doc->setFieldBoost('ID', false);
|
||||||
|
$doc->setFieldBoost('ClassHierarchy', false);
|
||||||
|
|
||||||
|
return $doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPublishing()
|
public function testPublishing()
|
||||||
{
|
{
|
||||||
|
|
||||||
// Setup mocks
|
|
||||||
$serviceMock = $this->getServiceMock();
|
|
||||||
self::$index->setService($serviceMock);
|
|
||||||
|
|
||||||
// Check that write updates Stage
|
// Check that write updates Stage
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::set_stage('Stage');
|
||||||
Phockito::reset($serviceMock);
|
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
||||||
$item->write();
|
$item->write();
|
||||||
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
|
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
|
||||||
$object->write();
|
$object->write();
|
||||||
|
|
||||||
|
$doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Stage');
|
||||||
|
$doc2 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Stage');
|
||||||
|
|
||||||
|
// Ensure correct call is made to Solr
|
||||||
|
$this->getServiceMock(['addDocument', 'commit'])
|
||||||
|
->expects($this->exactly(2))
|
||||||
|
->method('addDocument')
|
||||||
|
->withConsecutive(
|
||||||
|
[
|
||||||
|
$this->equalTo($doc1),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo($doc2),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$doc1 = new SolrDocumentMatcher(array(
|
|
||||||
'_documentid' => $this->getExpectedDocumentId($item, 'Stage'),
|
|
||||||
'ClassName' => 'SearchVariantVersionedTest_Item',
|
|
||||||
'SearchVariantVersionedTest_Item_TestText' => 'Foo',
|
|
||||||
'_versionedstage' => 'Stage'
|
|
||||||
));
|
|
||||||
$doc2 = new SolrDocumentMatcher(array(
|
|
||||||
'_documentid' => $this->getExpectedDocumentId($object, 'Stage'),
|
|
||||||
'ClassName' => 'SolrIndexVersionedTest_Object',
|
|
||||||
'SolrIndexVersionedTest_Object_TestText' => 'Bar',
|
|
||||||
'_versionedstage' => 'Stage'
|
|
||||||
));
|
|
||||||
Phockito::verify($serviceMock)->addDocument($doc1);
|
|
||||||
Phockito::verify($serviceMock)->addDocument($doc2);
|
|
||||||
|
|
||||||
// Check that write updates Live
|
// Check that write updates Live
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::set_stage('Stage');
|
||||||
Phockito::reset($serviceMock);
|
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
||||||
$item->write();
|
$item->write();
|
||||||
$item->publish('Stage', 'Live');
|
$item->copyVersionToStage('Stage', 'Live');
|
||||||
|
|
||||||
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
|
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
|
||||||
$object->write();
|
$object->write();
|
||||||
$object->publish('Stage', 'Live');
|
$object->copyVersionToStage('Stage', 'Live');
|
||||||
|
|
||||||
|
$doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Stage');
|
||||||
|
$doc2 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Live');
|
||||||
|
$doc3 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Stage');
|
||||||
|
$doc4 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Live');
|
||||||
|
|
||||||
|
// Ensure correct call is made to Solr
|
||||||
|
$this->getServiceMock(['addDocument', 'commit'])
|
||||||
|
->expects($this->exactly(4))
|
||||||
|
->method('addDocument')
|
||||||
|
->withConsecutive(
|
||||||
|
[
|
||||||
|
$this->equalTo($doc1),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo($doc2),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo($doc3),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo($doc4),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything(),
|
||||||
|
$this->anything()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
$doc = new SolrDocumentMatcher(array(
|
|
||||||
'_documentid' => $this->getExpectedDocumentId($item, 'Live'),
|
|
||||||
'ClassName' => 'SearchVariantVersionedTest_Item',
|
|
||||||
'SearchVariantVersionedTest_Item_TestText' => 'Foo',
|
|
||||||
'_versionedstage' => 'Live'
|
|
||||||
));
|
|
||||||
$doc2 = new SolrDocumentMatcher(array(
|
|
||||||
'_documentid' => $this->getExpectedDocumentId($object, 'Live'),
|
|
||||||
'ClassName' => 'SolrIndexVersionedTest_Object',
|
|
||||||
'SolrIndexVersionedTest_Object_TestText' => 'Bar',
|
|
||||||
'_versionedstage' => 'Live'
|
|
||||||
));
|
|
||||||
Phockito::verify($serviceMock)->addDocument($doc);
|
|
||||||
Phockito::verify($serviceMock)->addDocument($doc2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDelete()
|
public function testDelete()
|
||||||
{
|
{
|
||||||
// Setup mocks
|
|
||||||
$serviceMock = $this->getServiceMock();
|
|
||||||
self::$index->setService($serviceMock);
|
|
||||||
|
|
||||||
// Delete the live record (not the stage)
|
// Delete the live record (not the stage)
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::set_stage('Stage');
|
||||||
Phockito::reset($serviceMock);
|
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
|
||||||
$item->write();
|
$item->write();
|
||||||
$item->publish('Stage', 'Live');
|
$item->copyVersionToStage('Stage', 'Live');
|
||||||
Versioned::reading_stage('Live');
|
Versioned::set_stage('Live');
|
||||||
$id = clone $item;
|
$id = clone $item;
|
||||||
$item->delete();
|
$item->delete();
|
||||||
|
|
||||||
|
// Check that only the 'Live' version is deleted
|
||||||
|
$this->getServiceMock(['addDocument', 'commit', 'deleteById'])
|
||||||
|
->expects($this->exactly(1))
|
||||||
|
->method('deleteById')
|
||||||
|
->with($this->equalTo($this->getExpectedDocumentId($id, 'Live')));
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
Phockito::verify($serviceMock, 1)
|
|
||||||
->deleteById($this->getExpectedDocumentId($id, 'Live'));
|
|
||||||
Phockito::verify($serviceMock, 0)
|
|
||||||
->deleteById($this->getExpectedDocumentId($id, 'Stage'));
|
|
||||||
|
|
||||||
// Delete the stage record
|
// Delete the stage record
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::set_stage('Stage');
|
||||||
Phockito::reset($serviceMock);
|
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
|
||||||
$item->write();
|
$item->write();
|
||||||
$item->publish('Stage', 'Live');
|
$item->copyVersionToStage('Stage', 'Live');
|
||||||
$id = clone $item;
|
$id = clone $item;
|
||||||
$item->delete();
|
$item->delete();
|
||||||
|
|
||||||
|
// Check that only the 'Stage' version is deleted
|
||||||
|
$this->getServiceMock(['addDocument', 'commit', 'deleteById'])
|
||||||
|
->expects($this->exactly(1))
|
||||||
|
->method('deleteById')
|
||||||
|
->with($this->equalTo($this->getExpectedDocumentId($id, 'Stage')));
|
||||||
|
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
Phockito::verify($serviceMock, 1)
|
|
||||||
->deleteById($this->getExpectedDocumentId($id, 'Stage'));
|
|
||||||
Phockito::verify($serviceMock, 0)
|
|
||||||
->deleteById($this->getExpectedDocumentId($id, 'Live'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SolrVersionedTest_Index extends SolrIndex
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SearchVariantVersionedTest_Item');
|
|
||||||
$this->addClass('SolrIndexVersionedTest_Object');
|
|
||||||
$this->addFilterField('TestText');
|
|
||||||
$this->addFulltextField('Content');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-sitetree versioned dataobject
|
|
||||||
*/
|
|
||||||
class SolrIndexVersionedTest_Object extends DataObject implements TestOnly {
|
|
||||||
|
|
||||||
private static $extensions = array(
|
|
||||||
'Versioned'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $db = array(
|
|
||||||
'Title' => 'Varchar',
|
|
||||||
'Content' => 'Text',
|
|
||||||
'TestText' => 'Varchar',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrDocumentMatcher extends Hamcrest_BaseMatcher
|
|
||||||
{
|
|
||||||
protected $properties;
|
|
||||||
|
|
||||||
public function __construct($properties)
|
|
||||||
{
|
|
||||||
$this->properties = $properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function describeTo(\Hamcrest_Description $description)
|
|
||||||
{
|
|
||||||
$description->appendText('Apache_Solr_Document with properties '.var_export($this->properties, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function matches($item)
|
|
||||||
{
|
|
||||||
if (! ($item instanceof Apache_Solr_Document)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->properties as $key => $value) {
|
|
||||||
if ($item->{$key} != $value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
tests/SolrIndexVersionedTest/SolrDocumentMatcher.php
Normal file
39
tests/SolrIndexVersionedTest/SolrDocumentMatcher.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest;
|
||||||
|
|
||||||
|
if (!class_exists('\Phockito')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
\Phockito::include_hamcrest(false);
|
||||||
|
|
||||||
|
class SolrDocumentMatcher extends \Hamcrest_BaseMatcher
|
||||||
|
{
|
||||||
|
protected $properties;
|
||||||
|
|
||||||
|
public function __construct($properties)
|
||||||
|
{
|
||||||
|
$this->properties = $properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeTo(\Hamcrest_Description $description)
|
||||||
|
{
|
||||||
|
$description->appendText('\Apache_Solr_Document with properties '.var_export($this->properties, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function matches($item)
|
||||||
|
{
|
||||||
|
if (! ($item instanceof \Apache_Solr_Document)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->properties as $key => $value) {
|
||||||
|
if ($item->{$key} != $value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-sitetree versioned dataobject
|
||||||
|
*/
|
||||||
|
class SolrIndexVersionedTest_Object extends DataObject implements TestOnly
|
||||||
|
{
|
||||||
|
|
||||||
|
private static $table_name = 'SolrIndexVersionedTest_Object';
|
||||||
|
|
||||||
|
private static $extensions = [
|
||||||
|
Versioned::class
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $db = [
|
||||||
|
'Title' => 'Varchar',
|
||||||
|
'Content' => 'Text',
|
||||||
|
'TestText' => 'Varchar',
|
||||||
|
];
|
||||||
|
}
|
18
tests/SolrIndexVersionedTest/SolrVersionedTest_Index.php
Normal file
18
tests/SolrIndexVersionedTest/SolrVersionedTest_Index.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest\SolrIndexVersionedTest_Object;
|
||||||
|
|
||||||
|
class SolrVersionedTest_Index extends SolrIndex
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SearchVariantVersionedTest_Item::class);
|
||||||
|
$this->addClass(SolrIndexVersionedTest_Object::class);
|
||||||
|
$this->addFilterField('TestText');
|
||||||
|
$this->addFulltextField('Content');
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexQueuedHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexQueuedJob;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexGroupQueuedJob;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Variant;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_RecordingLogger;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexQueuedTest\SolrReindexQueuedTest_Service;
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJob;
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional tests of solr reindexing processes when run with queuedjobs
|
* Additional tests of solr reindexing processes when run with queuedjobs
|
||||||
*/
|
*/
|
||||||
@ -7,8 +28,8 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
protected $usesDatabase = true;
|
protected $usesDatabase = true;
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected static $extra_dataobjects = array(
|
||||||
'SolrReindexTest_Item'
|
SolrReindexTest_Item::class
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,28 +50,23 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
if (!interface_exists('SilverStripe\QueuedJobs\Services\QueuedJob')) {
|
||||||
$this->skipTest = true;
|
|
||||||
return $this->markTestSkipped("These tests need the Phockito module installed to run");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!interface_exists('QueuedJob')) {
|
|
||||||
$this->skipTest = true;
|
$this->skipTest = true;
|
||||||
return $this->markTestSkipped("These tests need the QueuedJobs module installed to run");
|
return $this->markTestSkipped("These tests need the QueuedJobs module installed to run");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set queued handler for reindex
|
// Set queued handler for reindex
|
||||||
Config::inst()->update('Injector', 'SolrReindexHandler', array(
|
Config::modify()->set(Injector::class, SolrReindexHandler::class, array(
|
||||||
'class' => 'SolrReindexQueuedHandler'
|
'class' => SolrReindexQueuedHandler::class
|
||||||
));
|
));
|
||||||
Injector::inst()->registerService(new SolrReindexQueuedHandler(), 'SolrReindexHandler');
|
Injector::inst()->registerService(new SolrReindexQueuedHandler(), SolrReindexHandler::class);
|
||||||
|
|
||||||
// Set test variant
|
// Set test variant
|
||||||
SolrReindexTest_Variant::enable();
|
SolrReindexTest_Variant::enable();
|
||||||
|
|
||||||
// Set index list
|
// Set index list
|
||||||
$this->service = $this->getServiceMock();
|
$this->service = $this->serviceMock();
|
||||||
$this->index = singleton('SolrReindexTest_Index');
|
$this->index = singleton(SolrReindexTest_Index::class);
|
||||||
$this->index->setService($this->service);
|
$this->index->setService($this->service);
|
||||||
FullTextSearch::force_index_list($this->index);
|
FullTextSearch::force_index_list($this->index);
|
||||||
}
|
}
|
||||||
@ -63,7 +79,8 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
protected function createDummyData($number)
|
protected function createDummyData($number)
|
||||||
{
|
{
|
||||||
// Populate dataobjects. Use truncate to generate predictable IDs
|
// Populate dataobjects. Use truncate to generate predictable IDs
|
||||||
DB::query('TRUNCATE "SolrReindexTest_Item"');
|
$tableName = DataObject::getSchema()->tableName(SolrReindexTest_Item::class);
|
||||||
|
DB::get_conn()->clearTable($tableName);
|
||||||
|
|
||||||
// Note that we don't create any records in variant = 2, to represent a variant
|
// Note that we don't create any records in variant = 2, to represent a variant
|
||||||
// that should be cleared without any re-indexes performed
|
// that should be cleared without any re-indexes performed
|
||||||
@ -82,9 +99,15 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
*
|
*
|
||||||
* @return SolrService
|
* @return SolrService
|
||||||
*/
|
*/
|
||||||
protected function getServiceMock()
|
protected function serviceMock()
|
||||||
{
|
{
|
||||||
return Phockito::mock('Solr4Service');
|
// Setup mock
|
||||||
|
/** @var SilverStripe\FullTextSearch\Solr\Services\Solr4Service|ObjectProphecy $serviceMock */
|
||||||
|
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
||||||
|
->setMethods(['deleteByQuery', 'addDocument'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
return $serviceMock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
@ -101,7 +124,7 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected function getHandler()
|
protected function getHandler()
|
||||||
{
|
{
|
||||||
return Injector::inst()->get('SolrReindexHandler');
|
return Injector::inst()->get(SolrReindexHandler::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +132,7 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected function getQueuedJobService()
|
protected function getQueuedJobService()
|
||||||
{
|
{
|
||||||
return singleton('SolrReindexQueuedTest_Service');
|
return Injector::inst()->get(SolrReindexQueuedTest_Service::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,6 +143,20 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
$this->createDummyData(18);
|
$this->createDummyData(18);
|
||||||
|
|
||||||
|
// Deletes are performed in the main task prior to individual groups being processed
|
||||||
|
// 18 records means 3 groups of 6 in each variant (6 total)
|
||||||
|
// Ensure correct call is made to Solr
|
||||||
|
$this->service->expects($this->exactly(2))
|
||||||
|
->method('deleteByQuery')
|
||||||
|
->withConsecutive(
|
||||||
|
[
|
||||||
|
$this->equalTo('-(ClassHierarchy:' . SolrReindexTest_Item::class . ')')
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->equalTo('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +(_testvariant:"2")')
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
// Create pre-existing jobs
|
// Create pre-existing jobs
|
||||||
$this->getQueuedJobService()->queueJob(new SolrReindexQueuedJob());
|
$this->getQueuedJobService()->queueJob(new SolrReindexQueuedJob());
|
||||||
$this->getQueuedJobService()->queueJob(new SolrReindexGroupQueuedJob());
|
$this->getQueuedJobService()->queueJob(new SolrReindexGroupQueuedJob());
|
||||||
@ -135,7 +172,7 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
|
|
||||||
// Next job should be queue job
|
// Next job should be queue job
|
||||||
$job = $this->getQueuedJobService()->getNextJob();
|
$job = $this->getQueuedJobService()->getNextJob();
|
||||||
$this->assertInstanceOf('SolrReindexQueuedJob', $job);
|
$this->assertInstanceOf(SolrReindexQueuedJob::class, $job);
|
||||||
$this->assertEquals(6, $job->getBatchSize());
|
$this->assertEquals(6, $job->getBatchSize());
|
||||||
|
|
||||||
// Test that necessary items are created
|
// Test that necessary items are created
|
||||||
@ -143,28 +180,19 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
$job->setLogger($logger);
|
$job->setLogger($logger);
|
||||||
$job->process();
|
$job->process();
|
||||||
|
|
||||||
// Deletes are performed in the main task prior to individual groups being processed
|
|
||||||
// 18 records means 3 groups of 6 in each variant (6 total)
|
|
||||||
Phockito::verify($this->service, 2)
|
|
||||||
->deleteByQuery(\Hamcrest_Matchers::anything());
|
|
||||||
$this->assertEquals(1, $logger->countMessages('Beginning init of reindex'));
|
$this->assertEquals(1, $logger->countMessages('Beginning init of reindex'));
|
||||||
$this->assertEquals(6, $logger->countMessages('Queued Solr Reindex Group '));
|
$this->assertEquals(6, $logger->countMessages('Queued Solr Reindex Group '));
|
||||||
$this->assertEquals(3, $logger->countMessages(' of SolrReindexTest_Item in {"SolrReindexTest_Variant":"0"}'));
|
$this->assertEquals(3, $logger->countMessages(' of ' . SolrReindexTest_Item::class . ' in {' . json_encode(SolrReindexTest_Variant::class) . ':"0"}'));
|
||||||
$this->assertEquals(3, $logger->countMessages(' of SolrReindexTest_Item in {"SolrReindexTest_Variant":"1"}'));
|
$this->assertEquals(3, $logger->countMessages(' of ' . SolrReindexTest_Item::class . ' in {' . json_encode(SolrReindexTest_Variant::class) . ':"1"}'));
|
||||||
$this->assertEquals(1, $logger->countMessages('Completed init of reindex'));
|
$this->assertEquals(1, $logger->countMessages('Completed init of reindex'));
|
||||||
|
|
||||||
|
|
||||||
// Test that invalid classes are removed
|
// Test that invalid classes are removed
|
||||||
$this->assertNotEmpty($logger->getMessages('Clearing obsolete classes from SolrReindexTest_Index'));
|
$this->assertNotEmpty($logger->getMessages('Clearing obsolete classes from ' . SolrReindexTest_Index::class));
|
||||||
Phockito::verify($this->service, 1)
|
|
||||||
->deleteByQuery('-(ClassHierarchy:SolrReindexTest_Item)');
|
|
||||||
|
|
||||||
// Test that valid classes in invalid variants are removed
|
// Test that valid classes in invalid variants are removed
|
||||||
$this->assertNotEmpty($logger->getMessages(
|
$this->assertNotEmpty($logger->getMessages(
|
||||||
'Clearing all records of type SolrReindexTest_Item in the current state: {"SolrReindexTest_Variant":"2"}'
|
'Clearing all records of type ' . SolrReindexTest_Item::class . ' in the current state: {"' . SolrReindexTest_Variant::class . '":"2"}'
|
||||||
));
|
));
|
||||||
Phockito::verify($this->service, 1)
|
|
||||||
->deleteByQuery('+(ClassHierarchy:SolrReindexTest_Item) +(_testvariant:"2")');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,9 +211,9 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
|
|
||||||
// Check next job is a group queued job
|
// Check next job is a group queued job
|
||||||
$job = $this->getQueuedJobService()->getNextJob();
|
$job = $this->getQueuedJobService()->getNextJob();
|
||||||
$this->assertInstanceOf('SolrReindexGroupQueuedJob', $job);
|
$this->assertInstanceOf(SolrReindexGroupQueuedJob::class, $job);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'Solr Reindex Group (1/3) of SolrReindexTest_Item in {"SolrReindexTest_Variant":"0"}',
|
'Solr Reindex Group (1/3) of ' . SolrReindexTest_Item::class . ' in {' . json_encode(SolrReindexTest_Variant::class) . ':"0"}',
|
||||||
$job->getTitle()
|
$job->getTitle()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -196,7 +224,7 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
|
|
||||||
// Check tasks completed (as per non-queuedjob version)
|
// Check tasks completed (as per non-queuedjob version)
|
||||||
$this->assertEquals(1, $logger->countMessages('Beginning reindex group'));
|
$this->assertEquals(1, $logger->countMessages('Beginning reindex group'));
|
||||||
$this->assertEquals(1, $logger->countMessages('Adding SolrReindexTest_Item'));
|
$this->assertEquals(1, $logger->countMessages('Adding ' . SolrReindexTest_Item::class . ''));
|
||||||
$this->assertEquals(1, $logger->countMessages('Queuing commit on all changes'));
|
$this->assertEquals(1, $logger->countMessages('Queuing commit on all changes'));
|
||||||
$this->assertEquals(1, $logger->countMessages('Completed reindex group'));
|
$this->assertEquals(1, $logger->countMessages('Completed reindex group'));
|
||||||
|
|
||||||
@ -211,19 +239,3 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!class_exists('QueuedJobService')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SolrReindexQueuedTest_Service extends QueuedJobService implements TestOnly
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return QueuedJob
|
|
||||||
*/
|
|
||||||
public function getNextJob()
|
|
||||||
{
|
|
||||||
$job = $this->getNextPendingJob();
|
|
||||||
return $this->initialiseJob($job);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexQueuedTest;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
|
if (!class_exists('SilverStripe\QueuedJobs\Services\QueuedJobService')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
use SilverStripe\QueuedJobs\Services\QueuedJobService;
|
||||||
|
|
||||||
|
class SolrReindexQueuedTest_Service extends QueuedJobService implements TestOnly
|
||||||
|
{
|
||||||
|
private static $dependencies = [
|
||||||
|
'queueHandler' => '%$QueueHandler'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return QueuedJob
|
||||||
|
*/
|
||||||
|
public function getNextJob()
|
||||||
|
{
|
||||||
|
$job = $this->getNextPendingJob();
|
||||||
|
return $this->initialiseJob($job);
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Monolog\Handler\AbstractProcessingHandler;
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
use Monolog\Handler\HandlerInterface;
|
|
||||||
use Monolog\Logger;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
if (class_exists('Phockito')) {
|
use SilverStripe\Dev\SapphireTest;
|
||||||
Phockito::include_hamcrest(false);
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
}
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Variant;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_TestHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_RecordingLogger;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Tasks\Solr_Reindex;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
|
||||||
class SolrReindexTest extends SapphireTest
|
class SolrReindexTest extends SapphireTest
|
||||||
{
|
{
|
||||||
protected $usesDatabase = true;
|
protected $usesDatabase = true;
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected static $extra_dataobjects = array(
|
||||||
'SolrReindexTest_Item'
|
SolrReindexTest_Item::class
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,28 +41,27 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected $service = null;
|
protected $service = null;
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
if (!class_exists('Phockito')) {
|
|
||||||
$this->skipTest = true;
|
|
||||||
return $this->markTestSkipped("These tests need the Phockito module installed to run");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set test handler for reindex
|
// Set test handler for reindex
|
||||||
Config::inst()->update('Injector', 'SolrReindexHandler', array(
|
Config::modify()->set(Injector::class, SolrReindexHandler::class, array(
|
||||||
'class' => 'SolrReindexTest_TestHandler'
|
'class' => SolrReindexTest_TestHandler::class
|
||||||
));
|
));
|
||||||
Injector::inst()->registerService(new SolrReindexTest_TestHandler(), 'SolrReindexHandler');
|
|
||||||
|
Injector::inst()->registerService(new SolrReindexTest_TestHandler(), SolrReindexHandler::class);
|
||||||
|
|
||||||
// Set test variant
|
// Set test variant
|
||||||
SolrReindexTest_Variant::enable();
|
SolrReindexTest_Variant::enable();
|
||||||
|
|
||||||
// Set index list
|
// Set index list
|
||||||
$this->service = $this->getServiceMock();
|
$this->service = $this->getServiceMock();
|
||||||
$this->index = singleton('SolrReindexTest_Index');
|
$this->index = singleton(SolrReindexTest_Index::class);
|
||||||
$this->index->setService($this->service);
|
$this->index->setService($this->service);
|
||||||
|
|
||||||
FullTextSearch::force_index_list($this->index);
|
FullTextSearch::force_index_list($this->index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +72,7 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected function createDummyData($number)
|
protected function createDummyData($number)
|
||||||
{
|
{
|
||||||
// Populate dataobjects. Use truncate to generate predictable IDs
|
self::resetDBSchema();
|
||||||
DB::query('TRUNCATE "SolrReindexTest_Item"');
|
|
||||||
|
|
||||||
// Note that we don't create any records in variant = 2, to represent a variant
|
// Note that we don't create any records in variant = 2, to represent a variant
|
||||||
// that should be cleared without any re-indexes performed
|
// that should be cleared without any re-indexes performed
|
||||||
@ -85,7 +93,10 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected function getServiceMock()
|
protected function getServiceMock()
|
||||||
{
|
{
|
||||||
return Phockito::mock('Solr4Service');
|
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
||||||
|
->setMethods(['deleteByQuery', 'addDocument']);
|
||||||
|
|
||||||
|
return $serviceMock->getMock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
@ -102,7 +113,7 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
protected function getHandler()
|
protected function getHandler()
|
||||||
{
|
{
|
||||||
return Injector::inst()->get('SolrReindexHandler');
|
return Injector::inst()->get(SolrReindexHandler::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +125,7 @@ class SolrReindexTest extends SapphireTest
|
|||||||
$variant = SearchVariant::current_state();
|
$variant = SearchVariant::current_state();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array(
|
array(
|
||||||
"SolrReindexTest_Variant" => "0"
|
SolrReindexTest_Variant::class => "0"
|
||||||
),
|
),
|
||||||
$variant
|
$variant
|
||||||
);
|
);
|
||||||
@ -124,13 +135,13 @@ class SolrReindexTest extends SapphireTest
|
|||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
"SolrReindexTest_Variant" => "0"
|
SolrReindexTest_Variant::class => "0"
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
"SolrReindexTest_Variant" => "1"
|
SolrReindexTest_Variant::class => "1"
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
"SolrReindexTest_Variant" => "2"
|
SolrReindexTest_Variant::class => "2"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
$allStates
|
$allStates
|
||||||
@ -157,36 +168,36 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testReindexSegmentsGroups()
|
public function testReindexSegmentsGroups()
|
||||||
{
|
{
|
||||||
|
$this->service->method('deleteByQuery')
|
||||||
|
->withConsecutive(
|
||||||
|
['-(ClassHierarchy:' . SolrReindexTest_Item::class . ')'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +(_testvariant:"2")']
|
||||||
|
);
|
||||||
|
|
||||||
$this->createDummyData(120);
|
$this->createDummyData(120);
|
||||||
|
|
||||||
// Initiate re-index
|
// Initiate re-index
|
||||||
$logger = new SolrReindexTest_RecordingLogger();
|
$logger = new SolrReindexTest_RecordingLogger();
|
||||||
$this->getHandler()->runReindex($logger, 21, 'Solr_Reindex');
|
$this->getHandler()->runReindex($logger, 21, Solr_Reindex::class);
|
||||||
|
|
||||||
// Test that invalid classes are removed
|
// Test that invalid classes are removed
|
||||||
$this->assertNotEmpty($logger->getMessages('Clearing obsolete classes from SolrReindexTest_Index'));
|
$this->assertContains('Clearing obsolete classes from ' . SolrReindexTest_Index::class, $logger->getMessages());
|
||||||
Phockito::verify($this->service, 1)
|
//var_dump($logger->getMessages());
|
||||||
->deleteByQuery('-(ClassHierarchy:SolrReindexTest_Item)');
|
|
||||||
|
|
||||||
// Test that valid classes in invalid variants are removed
|
// Test that valid classes in invalid variants are removed
|
||||||
$this->assertNotEmpty($logger->getMessages(
|
$this->assertContains('Clearing all records of type ' . SolrReindexTest_Item::class . ' in the current state: {' . json_encode(SolrReindexTest_Variant::class) . ':"2"}', $logger->getMessages());
|
||||||
'Clearing all records of type SolrReindexTest_Item in the current state: {"SolrReindexTest_Variant":"2"}'
|
|
||||||
));
|
|
||||||
Phockito::verify($this->service, 1)
|
|
||||||
->deleteByQuery('+(ClassHierarchy:SolrReindexTest_Item) +(_testvariant:"2")');
|
|
||||||
|
|
||||||
// 120x2 grouped into groups of 21 results in 12 groups
|
// 120x2 grouped into groups of 21 results in 12 groups
|
||||||
$this->assertEquals(12, $logger->countMessages('Called processGroup with '));
|
$this->assertEquals(12, $logger->countMessages('Called processGroup with '));
|
||||||
$this->assertEquals(6, $logger->countMessages('{"SolrReindexTest_Variant":"0"}'));
|
$this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"0"}'));
|
||||||
$this->assertEquals(6, $logger->countMessages('{"SolrReindexTest_Variant":"1"}'));
|
$this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"1"}'));
|
||||||
|
|
||||||
// Given that there are two variants, there should be two group ids of each number
|
// Given that there are two variants, there should be two group ids of each number
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 0 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 0 of 6'));
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 1 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 1 of 6'));
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 2 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 2 of 6'));
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 3 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 3 of 6'));
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 4 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 4 of 6'));
|
||||||
$this->assertEquals(2, $logger->countMessages(' SolrReindexTest_Item, group 5 of 6'));
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 5 of 6'));
|
||||||
|
|
||||||
// Check various group sizes
|
// Check various group sizes
|
||||||
$logger->clear();
|
$logger->clear();
|
||||||
@ -208,25 +219,23 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testRunGroup()
|
public function testRunGroup()
|
||||||
{
|
{
|
||||||
|
$this->service->method('deleteByQuery')
|
||||||
|
->with('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")');
|
||||||
|
|
||||||
$this->createDummyData(120);
|
$this->createDummyData(120);
|
||||||
$logger = new SolrReindexTest_RecordingLogger();
|
$logger = new SolrReindexTest_RecordingLogger();
|
||||||
|
|
||||||
// Initiate re-index of third group (index 2 of 6)
|
// Initiate re-index of third group (index 2 of 6)
|
||||||
$state = array('SolrReindexTest_Variant' => '1');
|
$state = array(SolrReindexTest_Variant::class => '1');
|
||||||
$this->getHandler()->runGroup($logger, $this->index, $state, 'SolrReindexTest_Item', 6, 2);
|
$this->getHandler()->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, 2);
|
||||||
$idMessage = $logger->filterMessages('Updated ');
|
$idMessage = $logger->filterMessages('Updated ');
|
||||||
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches));
|
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches));
|
||||||
$ids = array_unique(explode(',', $matches['ids']));
|
$ids = array_unique(explode(',', $matches['ids']));
|
||||||
|
|
||||||
// Test successful
|
// Test successful
|
||||||
$this->assertNotEmpty($logger->getMessages('Adding SolrReindexTest_Item'));
|
$this->assertNotEmpty($logger->getMessages('Adding ' . SolrReindexTest_Item::class));
|
||||||
$this->assertNotEmpty($logger->getMessages('Done'));
|
$this->assertNotEmpty($logger->getMessages('Done'));
|
||||||
|
|
||||||
// Test that items in this variant / group are cleared from solr
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test that items in this variant / group are re-indexed
|
// Test that items in this variant / group are re-indexed
|
||||||
// 120 divided into 6 groups should be 20 at least (max 21)
|
// 120 divided into 6 groups should be 20 at least (max 21)
|
||||||
$this->assertEquals(21, count($ids), 'Group size is about 20', 1);
|
$this->assertEquals(21, count($ids), 'Group size is about 20', 1);
|
||||||
@ -241,16 +250,27 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testRunAllGroups()
|
public function testRunAllGroups()
|
||||||
{
|
{
|
||||||
|
$this->service->method('deleteByQuery')
|
||||||
|
->withConsecutive(
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=1 u=1}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=3 u=3}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=4 u=4}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=5 u=5}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=6 u=6}mod(ID, 6)" +(_testvariant:"1")']
|
||||||
|
);
|
||||||
|
|
||||||
$this->createDummyData(120);
|
$this->createDummyData(120);
|
||||||
$logger = new SolrReindexTest_RecordingLogger();
|
$logger = new SolrReindexTest_RecordingLogger();
|
||||||
|
|
||||||
// Test that running all groups covers the complete set of ids
|
// Test that running all groups covers the complete set of ids
|
||||||
$state = array('SolrReindexTest_Variant' => '1');
|
$state = array(SolrReindexTest_Variant::class => '1');
|
||||||
for ($i = 0; $i < 6; $i++) {
|
for ($i = 0; $i < 6; $i++) {
|
||||||
// See testReindexSegmentsGroups for test that each of these states is invoked during a full reindex
|
// See testReindexSegmentsGroups for test that each of these states is invoked during a full reindex
|
||||||
$this
|
$this
|
||||||
->getHandler()
|
->getHandler()
|
||||||
->runGroup($logger, $this->index, $state, 'SolrReindexTest_Item', 6, $i);
|
->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, $i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count all ids updated
|
// Count all ids updated
|
||||||
@ -262,301 +282,5 @@ class SolrReindexTest extends SapphireTest
|
|||||||
|
|
||||||
// Check ids
|
// Check ids
|
||||||
$this->assertEquals(120, count($ids));
|
$this->assertEquals(120, count($ids));
|
||||||
Phockito::verify($this->service, 6)->deleteByQuery(\Hamcrest_Matchers::anything());
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=1 u=1}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=3 u=3}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=4 u=4}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
Phockito::verify($this->service, 1)->deleteByQuery(
|
|
||||||
'+(ClassHierarchy:SolrReindexTest_Item) +_query_:"{!frange l=5 u=5}mod(ID, 6)" +(_testvariant:"1")'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a wrapper for testing SolrReindexBase
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_TestHandler extends SolrReindexBase
|
|
||||||
{
|
|
||||||
public function processGroup(
|
|
||||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group, $taskName
|
|
||||||
) {
|
|
||||||
$indexName = $indexInstance->getIndexName();
|
|
||||||
$stateName = json_encode($state);
|
|
||||||
$logger->info("Called processGroup with {$indexName}, {$stateName}, {$class}, group {$group} of {$groups}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
|
||||||
{
|
|
||||||
$logger->info("Called triggerReindex");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SolrReindexTest_Index extends SolrIndex implements TestOnly
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
$this->addClass('SolrReindexTest_Item');
|
|
||||||
$this->addAllFulltextFields();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does not have any variant extensions
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_Item extends DataObject implements TestOnly
|
|
||||||
{
|
|
||||||
private static $extensions = array(
|
|
||||||
'SolrReindexTest_ItemExtension'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $db = array(
|
|
||||||
'Title' => 'Varchar(255)',
|
|
||||||
'Variant' => 'Int(0)'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select only records in the current variant
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_ItemExtension extends DataExtension implements TestOnly
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Filter records on the current variant
|
|
||||||
*
|
|
||||||
* @param SQLQuery $query
|
|
||||||
* @param DataQuery $dataQuery
|
|
||||||
*/
|
|
||||||
public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null)
|
|
||||||
{
|
|
||||||
$variant = SolrReindexTest_Variant::get_current();
|
|
||||||
if ($variant !== null && !$query->filtersOnID()) {
|
|
||||||
$sqlVariant = Convert::raw2sql($variant);
|
|
||||||
$query->addWhere("\"Variant\" = '{$sqlVariant}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy variant that selects items with field Varient matching the current value
|
|
||||||
*
|
|
||||||
* Variant states are 0 and 1, or null if disabled
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_Variant extends SearchVariant implements TestOnly
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Value of this variant (either null, 0, or 1)
|
|
||||||
*
|
|
||||||
* @var int|null
|
|
||||||
*/
|
|
||||||
protected static $current = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate this variant
|
|
||||||
*/
|
|
||||||
public static function enable()
|
|
||||||
{
|
|
||||||
self::disable();
|
|
||||||
|
|
||||||
self::$current = 0;
|
|
||||||
self::$variants = array(
|
|
||||||
'SolrReindexTest_Variant' => singleton('SolrReindexTest_Variant')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable this variant and reset
|
|
||||||
*/
|
|
||||||
public static function disable()
|
|
||||||
{
|
|
||||||
self::$current = null;
|
|
||||||
self::$variants = null;
|
|
||||||
self::$class_variants = array();
|
|
||||||
self::$call_instances = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function activateState($state)
|
|
||||||
{
|
|
||||||
self::set_current($state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current variant to the given state
|
|
||||||
*
|
|
||||||
* @param int $current 0, 1, 2, or null (disabled)
|
|
||||||
*/
|
|
||||||
public static function set_current($current)
|
|
||||||
{
|
|
||||||
self::$current = $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current state
|
|
||||||
*
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public static function get_current()
|
|
||||||
{
|
|
||||||
// Always use string values for states for consistent json_encode value
|
|
||||||
if (isset(self::$current)) {
|
|
||||||
return (string)self::$current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function alterDefinition($class, $index)
|
|
||||||
{
|
|
||||||
$self = get_class($this);
|
|
||||||
|
|
||||||
$this->addFilterField($index, '_testvariant', array(
|
|
||||||
'name' => '_testvariant',
|
|
||||||
'field' => '_testvariant',
|
|
||||||
'fullfield' => '_testvariant',
|
|
||||||
'base' => ClassInfo::baseDataClass($class),
|
|
||||||
'origin' => $class,
|
|
||||||
'type' => 'Int',
|
|
||||||
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function alterQuery($query, $index)
|
|
||||||
{
|
|
||||||
// I guess just calling it _testvariant is ok?
|
|
||||||
$query->filter('_testvariant', $this->currentState());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function appliesTo($class, $includeSubclasses)
|
|
||||||
{
|
|
||||||
return $class === 'SolrReindexTest_Item' ||
|
|
||||||
($includeSubclasses && is_subclass_of($class, 'SolrReindexTest_Item', true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function appliesToEnvironment()
|
|
||||||
{
|
|
||||||
// Set to null to disable
|
|
||||||
return self::$current !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentState()
|
|
||||||
{
|
|
||||||
return self::get_current();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reindexStates()
|
|
||||||
{
|
|
||||||
// Always use string values for states for consistent json_encode value
|
|
||||||
return array('0', '1', '2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test logger for recording messages
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_RecordingLogger extends Logger implements TestOnly
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var SolrReindexTest_Handler
|
|
||||||
*/
|
|
||||||
protected $testHandler = null;
|
|
||||||
|
|
||||||
public function __construct($name = 'testlogger', array $handlers = array(), array $processors = array())
|
|
||||||
{
|
|
||||||
parent::__construct($name, $handlers, $processors);
|
|
||||||
|
|
||||||
$this->testHandler = new SolrReindexTest_Handler();
|
|
||||||
$this->pushHandler($this->testHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getMessages()
|
|
||||||
{
|
|
||||||
return $this->testHandler->getMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all messages
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->testHandler->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get messages with the given filter
|
|
||||||
*
|
|
||||||
* @param string $containing
|
|
||||||
* @return array Filtered array
|
|
||||||
*/
|
|
||||||
public function filterMessages($containing)
|
|
||||||
{
|
|
||||||
return array_values(array_filter(
|
|
||||||
$this->getMessages(),
|
|
||||||
function ($content) use ($containing) {
|
|
||||||
return stripos($content, $containing) !== false;
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count all messages containing the given substring
|
|
||||||
*
|
|
||||||
* @param string $containing Message to filter by
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function countMessages($containing = null)
|
|
||||||
{
|
|
||||||
if ($containing) {
|
|
||||||
$messages = $this->filterMessages($containing);
|
|
||||||
} else {
|
|
||||||
$messages = $this->getMessages();
|
|
||||||
}
|
|
||||||
return count($messages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for recording messages for later retrieval
|
|
||||||
*/
|
|
||||||
class SolrReindexTest_Handler extends AbstractProcessingHandler implements TestOnly
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Messages
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $messages = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all messages
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getMessages()
|
|
||||||
{
|
|
||||||
return $this->messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->messages = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function write(array $record)
|
|
||||||
{
|
|
||||||
$this->messages[] = $record['message'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
tests/SolrReindexTest/SolrReindexTest_Handler.php
Normal file
39
tests/SolrReindexTest/SolrReindexTest_Handler.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use Monolog\Handler\AbstractProcessingHandler;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for recording messages for later retrieval
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_Handler extends AbstractProcessingHandler implements TestOnly
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Messages
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $messages = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all messages
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMessages()
|
||||||
|
{
|
||||||
|
return $this->messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->messages = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function write(array $record)
|
||||||
|
{
|
||||||
|
$this->messages[] = $record['message'];
|
||||||
|
}
|
||||||
|
}
|
15
tests/SolrReindexTest/SolrReindexTest_Index.php
Normal file
15
tests/SolrReindexTest/SolrReindexTest_Index.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
|
||||||
|
class SolrReindexTest_Index extends SolrIndex implements TestOnly
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->addClass(SolrReindexTest_Item::class);
|
||||||
|
$this->addAllFulltextFields();
|
||||||
|
}
|
||||||
|
}
|
24
tests/SolrReindexTest/SolrReindexTest_Item.php
Normal file
24
tests/SolrReindexTest/SolrReindexTest_Item.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_ItemExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not have any variant extensions
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_Item extends DataObject
|
||||||
|
{
|
||||||
|
private static $table_name = 'SolrReindexTest_Item';
|
||||||
|
|
||||||
|
private static $extensions = [
|
||||||
|
SolrReindexTest_ItemExtension::class
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'Title' => 'Varchar(255)',
|
||||||
|
'Variant' => 'Int(0)'
|
||||||
|
);
|
||||||
|
}
|
31
tests/SolrReindexTest/SolrReindexTest_ItemExtension.php
Normal file
31
tests/SolrReindexTest/SolrReindexTest_ItemExtension.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DataExtension;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\ORM\Queries\SQLSelect;
|
||||||
|
use SilverStripe\ORM\DataQuery;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Variant;
|
||||||
|
use SilverStripe\Core\Convert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select only records in the current variant
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_ItemExtension extends DataExtension implements TestOnly
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Filter records on the current variant
|
||||||
|
*
|
||||||
|
* @param SQLSelect $query
|
||||||
|
* @param DataQuery $dataQuery
|
||||||
|
*/
|
||||||
|
public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
|
||||||
|
{
|
||||||
|
$variant = SolrReindexTest_Variant::get_current();
|
||||||
|
if ($variant !== null && !$query->filtersOnID()) {
|
||||||
|
$sqlVariant = Convert::raw2sql($variant);
|
||||||
|
$query->addWhere("\"Variant\" = '{$sqlVariant}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
tests/SolrReindexTest/SolrReindexTest_RecordingLogger.php
Normal file
74
tests/SolrReindexTest/SolrReindexTest_RecordingLogger.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test logger for recording messages
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_RecordingLogger extends Logger implements TestOnly
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SolrReindexTest_Handler
|
||||||
|
*/
|
||||||
|
protected $testHandler = null;
|
||||||
|
|
||||||
|
public function __construct($name = 'testlogger', array $handlers = array(), array $processors = array())
|
||||||
|
{
|
||||||
|
parent::__construct($name, $handlers, $processors);
|
||||||
|
|
||||||
|
$this->testHandler = new SolrReindexTest_Handler();
|
||||||
|
$this->pushHandler($this->testHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMessages()
|
||||||
|
{
|
||||||
|
return $this->testHandler->getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all messages
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->testHandler->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get messages with the given filter
|
||||||
|
*
|
||||||
|
* @param string $containing
|
||||||
|
* @return array Filtered array
|
||||||
|
*/
|
||||||
|
public function filterMessages($containing)
|
||||||
|
{
|
||||||
|
return array_values(array_filter(
|
||||||
|
$this->getMessages(),
|
||||||
|
function ($content) use ($containing) {
|
||||||
|
return stripos($content, $containing) !== false;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count all messages containing the given substring
|
||||||
|
*
|
||||||
|
* @param string $containing Message to filter by
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function countMessages($containing = null)
|
||||||
|
{
|
||||||
|
if ($containing) {
|
||||||
|
$messages = $this->filterMessages($containing);
|
||||||
|
} else {
|
||||||
|
$messages = $this->getMessages();
|
||||||
|
}
|
||||||
|
return count($messages);
|
||||||
|
}
|
||||||
|
}
|
32
tests/SolrReindexTest/SolrReindexTest_TestHandler.php
Normal file
32
tests/SolrReindexTest/SolrReindexTest_TestHandler.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexBase;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a wrapper for testing SolrReindexBase
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_TestHandler extends SolrReindexBase
|
||||||
|
{
|
||||||
|
public function processGroup(
|
||||||
|
LoggerInterface $logger,
|
||||||
|
SolrIndex $indexInstance,
|
||||||
|
$state,
|
||||||
|
$class,
|
||||||
|
$groups,
|
||||||
|
$group,
|
||||||
|
$taskName
|
||||||
|
) {
|
||||||
|
$indexName = $indexInstance->getIndexName();
|
||||||
|
$stateName = json_encode($state);
|
||||||
|
$logger->info("Called processGroup with {$indexName}, {$stateName}, {$class}, group {$group} of {$groups}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||||
|
{
|
||||||
|
$logger->info("Called triggerReindex");
|
||||||
|
}
|
||||||
|
}
|
118
tests/SolrReindexTest/SolrReindexTest_Variant.php
Normal file
118
tests/SolrReindexTest/SolrReindexTest_Variant.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy variant that selects items with field Varient matching the current value
|
||||||
|
*
|
||||||
|
* Variant states are 0 and 1, or null if disabled
|
||||||
|
*/
|
||||||
|
class SolrReindexTest_Variant extends SearchVariant implements TestOnly
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Value of this variant (either null, 0, or 1)
|
||||||
|
*
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
protected static $current = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate this variant
|
||||||
|
*/
|
||||||
|
public static function enable()
|
||||||
|
{
|
||||||
|
self::disable();
|
||||||
|
|
||||||
|
self::$current = 0;
|
||||||
|
self::$variants = array(
|
||||||
|
self::class => singleton(self::class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable this variant and reset
|
||||||
|
*/
|
||||||
|
public static function disable()
|
||||||
|
{
|
||||||
|
self::$current = null;
|
||||||
|
self::$variants = null;
|
||||||
|
self::$class_variants = array();
|
||||||
|
self::$call_instances = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activateState($state)
|
||||||
|
{
|
||||||
|
self::set_current($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current variant to the given state
|
||||||
|
*
|
||||||
|
* @param int $current 0, 1, 2, or null (disabled)
|
||||||
|
*/
|
||||||
|
public static function set_current($current)
|
||||||
|
{
|
||||||
|
self::$current = $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current state
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function get_current()
|
||||||
|
{
|
||||||
|
// Always use string values for states for consistent json_encode value
|
||||||
|
if (isset(self::$current)) {
|
||||||
|
return (string)self::$current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterDefinition($class, $index)
|
||||||
|
{
|
||||||
|
$self = get_class($this);
|
||||||
|
|
||||||
|
$this->addFilterField($index, '_testvariant', array(
|
||||||
|
'name' => '_testvariant',
|
||||||
|
'field' => '_testvariant',
|
||||||
|
'fullfield' => '_testvariant',
|
||||||
|
'base' => DataObject::getSchema()->baseDataClass($class),
|
||||||
|
'origin' => $class,
|
||||||
|
'type' => 'Int',
|
||||||
|
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery($query, $index)
|
||||||
|
{
|
||||||
|
// I guess just calling it _testvariant is ok?
|
||||||
|
$query->filter('_testvariant', $this->currentState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appliesTo($class, $includeSubclasses)
|
||||||
|
{
|
||||||
|
return $class === SolrReindexTest_Item::class ||
|
||||||
|
($includeSubclasses && is_subclass_of($class, SolrReindexTest_Item::class, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appliesToEnvironment()
|
||||||
|
{
|
||||||
|
// Set to null to disable
|
||||||
|
return self::$current !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currentState()
|
||||||
|
{
|
||||||
|
return self::get_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reindexStates()
|
||||||
|
{
|
||||||
|
// Always use string values for states for consistent json_encode value
|
||||||
|
return array('0', '1', '2');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user