TASK: Finish deletion of index and documents feature

We replace the "flush" and "delete" by "delete" and "deletedocuments"
logic. This makes it more obvious what will happen, without reading the
docs.

Also we kept the logic to always provide the index name, as
we will need them in the future. Due to elasticsearch v6 changes no
types are allowed in the same index in the future. Therefore we need to
make it possible to use different indexes in the future, leading to the
need to provide the document type all the time.
This commit is contained in:
Daniel Siepmann 2018-12-30 13:43:43 +01:00
parent 28a8dd1ab4
commit 689f293194
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
15 changed files with 114 additions and 96 deletions

View file

@ -63,24 +63,24 @@ class IndexCommandController extends CommandController
* *
* @param string $identifier Comma separated list of identifiers. * @param string $identifier Comma separated list of identifiers.
*/ */
public function deleteCommand(string $identifiers) public function deleteDocumentsCommand(string $identifiers)
{ {
$this->executeForIdentifier($identifiers, function (IndexerInterface $indexer) { $this->executeForIdentifier($identifiers, function (IndexerInterface $indexer) {
$indexer->deleteDocuments(); $indexer->deleteAllDocuments();
$this->outputLine('Documents in index ' . $indexer->getIdentifier() . ' were deleted.'); $this->outputLine('Documents in index ' . $indexer->getIdentifier() . ' were deleted.');
}); });
} }
/** /**
* Will flush the index for given identifiers from backend. * Will delete the index for given identifiers.
* *
* @param string $identifier Comma separated list of identifiers. * @param string $identifier Comma separated list of identifiers.
*/ */
public function flushCommand(string $identifiers = 'pages') public function deleteCommand(string $identifiers = 'pages')
{ {
$this->executeForIdentifier($identifiers, function (IndexerInterface $indexer) { $this->executeForIdentifier($identifiers, function (IndexerInterface $indexer) {
$indexer->delete(); $indexer->delete();
$this->outputLine('Indice ' . $indexer->getIdentifier() . ' was flushed.'); $this->outputLine('Index ' . $indexer->getIdentifier() . ' was deleted.');
}); });
} }

View file

@ -51,17 +51,17 @@ interface ConnectionInterface
public function deleteDocument(string $documentType, string $identifier); public function deleteDocument(string $documentType, string $identifier);
/** /**
* Search by given request and return result. * Will all documents of certain kind / in certain index.
*/ */
public function search(SearchRequestInterface $searchRequest): SearchResultInterface; public function deleteAllDocuments(string $documentType);
/**
* Will delete the index / db of defined document type.
*/
public function deleteIndexByDocumentType(string $documentType);
/** /**
* Will delete the whole index / db. * Will delete the whole index / db.
*/ */
public function deleteIndex(); public function deleteIndex(string $documentType);
/**
* Search by given request and return result.
*/
public function search(SearchRequestInterface $searchRequest): SearchResultInterface;
} }

View file

@ -142,6 +142,29 @@ class Elasticsearch implements Singleton, ConnectionInterface
} }
} }
public function deleteAllDocuments(string $documentType)
{
$this->deleteDocumentsByQuery($documentType, Query::create([
'query' => [
'term' => [
'search_document_type' => $documentType,
],
],
]));
}
public function deleteIndex(string $documentType)
{
try {
$this->indexFactory->getIndex($this->connection, $documentType)->delete();
} catch (\InvalidArgumentException $e) {
$this->logger->notice(
'Index did not exist, therefore was not deleted.',
[$documentType, $e]
);
}
}
public function updateDocument(string $documentType, array $document) public function updateDocument(string $documentType, array $document)
{ {
$this->withType( $this->withType(
@ -162,49 +185,6 @@ class Elasticsearch implements Singleton, ConnectionInterface
); );
} }
public function deleteIndex()
{
$index = $this->connection->getClient()->getIndex($this->indexFactory->getIndexName());
if (!$index->exists()) {
$this->logger->notice(
'Index did not exist, therefore was not deleted.',
[$this->indexFactory->getIndexName()]
);
return;
}
$index->delete();
}
public function deleteIndexByDocumentType(string $documentType)
{
$this->deleteIndexByQuery(Query::create([
'query' => [
'term' => [
'search_document_type' => $documentType,
],
],
]));
}
private function deleteIndexByQuery(Query $query)
{
$index = $this->connection->getClient()->getIndex($this->indexFactory->getIndexName());
if (!$index->exists()) {
$this->logger->notice(
'Index did not exist, therefore items can not be deleted by query.',
[$this->indexFactory->getIndexName(), $query->getQuery()]
);
return;
}
$response = $index->deleteByQuery($query);
if ($response->getData()['deleted'] > 0) {
// Refresh index when delete query is invoked
$index->refresh();
}
}
public function search(SearchRequestInterface $searchRequest): SearchResultInterface public function search(SearchRequestInterface $searchRequest): SearchResultInterface
{ {
$this->logger->debug('Search for', [$searchRequest->getSearchTerm()]); $this->logger->debug('Search for', [$searchRequest->getSearchTerm()]);
@ -232,4 +212,22 @@ class Elasticsearch implements Singleton, ConnectionInterface
$callback($type, $documentType); $callback($type, $documentType);
$type->getIndex()->refresh(); $type->getIndex()->refresh();
} }
private function deleteDocumentsByQuery(string $documentType, Query $query)
{
try {
$index = $this->indexFactory->getIndex($this->connection, $documentType);
$response = $index->deleteByQuery($query);
if ($response->getData()['deleted'] > 0) {
// Refresh index when delete query is invoked
$index->refresh();
}
} catch (\InvalidArgumentException $e) {
$this->logger->notice(
'Index did not exist, therefore items can not be deleted by query.',
[$documentType, $query->getQuery()]
);
}
}
} }

View file

@ -70,18 +70,31 @@ class IndexFactory implements Singleton
} }
/** /**
* Get an index based on TYPO3 table name. * @throws \InvalidArgumentException If index does not exist.
*/ */
public function getIndex(Connection $connection, string $documentType): \Elastica\Index public function getIndex(Connection $connection, string $documentType): \Elastica\Index
{ {
$index = $connection->getClient()->getIndex($this->getIndexName()); $index = $connection->getClient()->getIndex($this->getIndexName());
if ($index->exists() === false) { if ($index->exists() === false) {
throw new \InvalidArgumentException('The requested index does not exist.', 1546173102);
}
return $index;
}
public function createIndex(Connection $connection, string $documentType): \Elastica\Index
{
$index = $connection->getClient()->getIndex($this->getIndexName());
if ($index->exists() === true) {
return $index;
}
$config = $this->getConfigurationFor($documentType); $config = $this->getConfigurationFor($documentType);
$this->logger->debug(sprintf('Create index %s.', $documentType), [$documentType, $config]); $this->logger->debug(sprintf('Create index %s.', $documentType), [$documentType, $config]);
$index->create($config); $index->create($config);
$this->logger->debug(sprintf('Created index %s.', $documentType), [$documentType]); $this->logger->debug(sprintf('Created index %s.', $documentType), [$documentType]);
}
return $index; return $index;
} }

View file

@ -49,12 +49,9 @@ class TypeFactory implements Singleton
$this->connection = $connection; $this->connection = $connection;
} }
/**
* Get an index bases on TYPO3 table name.
*/
public function getType(string $documentType): \Elastica\Type public function getType(string $documentType): \Elastica\Type
{ {
$index = $this->indexFactory->getIndex($this->connection, $documentType); $index = $this->indexFactory->createIndex($this->connection, $documentType);
return $index->getType('document'); return $index->getType('document');
} }
} }

View file

@ -106,14 +106,14 @@ abstract class AbstractIndexer implements IndexerInterface
public function delete() public function delete()
{ {
$this->logger->info('Start deletion of index.'); $this->logger->info('Start deletion of index.');
$this->connection->deleteIndex(); $this->connection->deleteIndex($this->getDocumentName());
$this->logger->info('Finish deletion.'); $this->logger->info('Finish deletion.');
} }
public function deleteDocuments() public function deleteAllDocuments()
{ {
$this->logger->info('Start deletion of indexed documents.'); $this->logger->info('Start deletion of indexed documents.');
$this->connection->deleteIndexByDocumentType($this->getDocumentName()); $this->connection->deleteAllDocuments($this->getDocumentName());
$this->logger->info('Finish deletion.'); $this->logger->info('Finish deletion.');
} }

View file

@ -42,9 +42,9 @@ interface IndexerInterface
public function delete(); public function delete();
/** /**
* Delete the whole index. * Delete all documents from index.
*/ */
public function deleteDocuments(); public function deleteAllDocuments();
/** /**
* Receives the identifier of the indexer itself. * Receives the identifier of the indexer itself.

View file

@ -8,7 +8,7 @@ v0.1.0
0.1.0/2018-changed-interfaces 0.1.0/2018-changed-interfaces
0.1.0/2018-elasticsearch-upgrade 0.1.0/2018-elasticsearch-upgrade
0.1.0/2018-search-service-interface 0.1.0/2018-search-service-interface
0.1.0/20181027-added-flush-command 0.1.0/20181027-added-delete-all-documents-command
0.1.0/20181027-allow-multiple-identifier-on-cli 0.1.0/20181027-allow-multiple-identifier-on-cli
0.1.0/20181027-remove-cms7-support 0.1.0/20181027-remove-cms7-support
0.1.0/20181028-fluid-templating-list-items 0.1.0/20181028-fluid-templating-list-items

View file

@ -5,7 +5,7 @@ Some interfaces and abstract classes have been adjusted:
``Codappix\SearchCore\Connection\ConnectionInterface``: ``Codappix\SearchCore\Connection\ConnectionInterface``:
* New method ``public function deleteIndexByDocumentType(string $documentType);`` * New method ``public function deleteAllDocuments(string $documentType);``
``Codappix\SearchCore\Domain\Index\IndexerInterface``: ``Codappix\SearchCore\Domain\Index\IndexerInterface``:
@ -28,4 +28,12 @@ Also some exceptions have changed:
throws an ``\InvalidArgumentException`` instead of ``\Exception``, if no throws an ``\InvalidArgumentException`` instead of ``\Exception``, if no
``search_identifier`` was provided. ``search_identifier`` was provided.
* ``Codappix\SearchCore\Connection\Elasticsearch\IndexFactory::getIndex()`` now
throws an ``\InvalidArgumentException`` if the index does not exist. Leaving
handling up to the caller.
Before the index was created if it didn't exist. To create an index, a new method
``public function createIndex(Connection $connection, string $documentType): \Elastica\Index``
was added. This method will only create the index if it didn't exist before.
In the end, the index is returned always. Making this method a 1:1 replacement for
older ``getIndex()``.

View file

@ -0,0 +1,11 @@
Feature "Added delete documents command"
========================================
A new command to delete all documents within an index was added. In contrast to the
existing delete command, this deletes only documents but keeps the index.
E.g. if your backend is Elasticsearch or a relational database, the index or table is
kept, including structure or mappings, while only the documents or rows are removed.
In contrast the existing delete command will still remove the index or table itself,
depending on the used connection.

View file

@ -1,6 +0,0 @@
Feature "Added flush command"
=============================
A new command to flush indices was added. In contrast to the existing delete command,
this one will delete the whole index, while the existing delete command only deletes
all documents within an index.

View file

@ -36,18 +36,17 @@ documents from the index.
Multiple indexes can be called by providing a comma separated list of identifiers as Multiple indexes can be called by providing a comma separated list of identifiers as
a single argument. Spaces before and after commas are ignored. a single argument. Spaces before and after commas are ignored.
.. _usage_manual_flush: .. _usage_manual_delete_all_documents:
Manual flush Manual delete all documents
------------ ---------------------------
You can trigger flush for indexes from CLI:: You can trigger deletion of all documents for indexes from CLI::
./typo3/cli_dispatch.phpsh extbase index:flush --identifiers 'pages' ./typo3/cli_dispatch.phpsh extbase index:deletedocuments --identifiers 'pages'
./bin/typo3cms index:flush --identifiers 'pages' ./bin/typo3cms index:deletedocuments --identifiers 'pages'
This will flush the index for the table ``pages``. Flush means removing the index This will delete all documents within the index for the table ``pages``.
from backend.
Multiple indexes can be called by providing a comma separated list of identifiers as Multiple indexes can be called by providing a comma separated list of identifiers as
a single argument. Spaces before and after commas are ignored. a single argument. Spaces before and after commas are ignored.

View file

@ -89,14 +89,12 @@ class IndexDeletionTest extends AbstractFunctionalTestCase
$response = $this->client->request('typo3content/_search?q=*:*'); $response = $this->client->request('typo3content/_search?q=*:*');
$this->assertSame($response->getData()['hits']['total'], 5, 'Not exactly 5 documents are in index.'); $this->assertSame($response->getData()['hits']['total'], 5, 'Not exactly 5 documents are in index.');
$contentIndexer->deleteDocuments(); $contentIndexer->deleteAllDocuments();
$response = $this->client->request('typo3content/_search?q=*:*'); $response = $this->client->request('typo3content/_search?q=*:*');
$this->assertSame($response->getData()['hits']['total'], 2, 'Not exactly 2 documents are in index.'); $this->assertSame($response->getData()['hits']['total'], 2, 'Not exactly 2 documents are in index.');
$pageIndexer->deleteDocuments(); $pageIndexer->deleteAllDocuments();
$response = $this->client->request('typo3content/_search?q=*:*'); $response = $this->client->request('typo3content/_search?q=*:*');
$this->assertSame($response->getData()['hits']['total'], 0, 'Index should be empty.'); $this->assertSame($response->getData()['hits']['total'], 0, 'Index should be empty.');
$index->delete();
} }
} }

View file

@ -97,7 +97,7 @@ class IndexCommandControllerTest extends AbstractUnitTestCase
/** /**
* @test * @test
*/ */
public function deletionIsPossible() public function deletionOfDocumentsIsPossible()
{ {
$indexerMock = $this->getMockBuilder(TcaIndexer::class) $indexerMock = $this->getMockBuilder(TcaIndexer::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
@ -114,14 +114,14 @@ class IndexCommandControllerTest extends AbstractUnitTestCase
->will($this->returnValue($indexerMock)); ->will($this->returnValue($indexerMock));
$indexerMock->expects($this->once()) $indexerMock->expects($this->once())
->method('deleteDocuments'); ->method('deleteAllDocuments');
$this->subject->deleteCommand('allowedTable'); $this->subject->deleteDocumentsCommand('allowedTable');
} }
/** /**
* @test * @test
*/ */
public function flushIsPossible() public function deletionOfIndexIsPossible()
{ {
$indexerMock = $this->getMockBuilder(TcaIndexer::class) $indexerMock = $this->getMockBuilder(TcaIndexer::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
@ -139,7 +139,7 @@ class IndexCommandControllerTest extends AbstractUnitTestCase
$indexerMock->expects($this->once()) $indexerMock->expects($this->once())
->method('delete'); ->method('delete');
$this->subject->flushCommand('pages'); $this->subject->deleteCommand('pages');
} }
/** /**

View file

@ -150,6 +150,6 @@ class IndexFactoryTest extends AbstractUnitTestCase
]) ])
); );
$this->subject->getIndex($connection, 'someIndex'); $this->subject->createIndex($connection, 'someIndex');
} }
} }