Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added support for Component Template [#2274](https://github.com/ruflin/Elastica/pull/2274)
* Added support for Index Template [#2274](https://github.com/ruflin/Elastica/pull/2274)
* Added Template class to target only legacy Template [#2274](https://github.com/ruflin/Elastica/pull/2274)
* Added support for `seq_no_primary_term` search option to retrieve sequence numbers and primary terms in search results
* Added support for `if_seq_no` and `if_primary_term` options in `Index::addDocument()` for optimistic concurrency control
### Changed
### Deprecated
### Removed
Expand Down
2 changes: 2 additions & 0 deletions src/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ public function addDocument(Document $doc): Response
$options = $doc->getOptions(
[
'consistency',
'if_seq_no',
'if_primary_term',
'op_type',
'parent',
'percolate',
Expand Down
2 changes: 2 additions & 0 deletions src/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Search
public const OPTION_SHARD_REQUEST_CACHE = 'request_cache';
public const OPTION_FILTER_PATH = 'filter_path';
public const OPTION_TYPED_KEYS = 'typed_keys';
public const OPTION_SEQ_NO_PRIMARY_TERM = 'seq_no_primary_term';

/*
* Search types
Expand Down Expand Up @@ -413,6 +414,7 @@ protected function validateOption(string $key): void
case self::OPTION_SHARD_REQUEST_CACHE:
case self::OPTION_FILTER_PATH:
case self::OPTION_TYPED_KEYS:
case self::OPTION_SEQ_NO_PRIMARY_TERM:
return;
}

Expand Down
114 changes: 114 additions & 0 deletions tests/IndexSeqNoPrimaryTermTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Elastica\Test;

use Elastica\Document;
use Elastica\Index;
use Elastica\Test\Base as BaseTest;

/**
* @group functional
*
* @internal
*/
class IndexSeqNoPrimaryTermTest extends BaseTest
{
private Index $index;

protected function setUp(): void
{
parent::setUp();

$this->index = $this->_createIndex();
}

protected function tearDown(): void
{
$this->index->delete();
parent::tearDown();
}

/**
* @covers \Elastica\Index::addDocument
* @covers \Elastica\AbstractUpdateAction::setSequenceNumber
* @covers \Elastica\AbstractUpdateAction::setPrimaryTerm
* @group functional
*/
public function testAddDocumentWithSeqNoPrimaryTerm(): void
{
$doc = new Document('1', ['title' => 'Test document']);
$doc->setSequenceNumber(1);
$doc->setPrimaryTerm(1);

$response = $this->index->addDocument($doc);

$this->assertTrue($response->isOk());
}

/**
* @covers \Elastica\Index::addDocument
* @covers \Elastica\AbstractUpdateAction::setSequenceNumber
* @covers \Elastica\AbstractUpdateAction::setPrimaryTerm
* @group functional
*/
public function testAddDocumentWithOptimisticConcurrencyControl(): void
{
// First, add a document
$doc1 = new Document('1', ['title' => 'Original document']);
$response1 = $this->index->addDocument($doc1);
$this->assertTrue($response1->isOk());

// Get the document to retrieve its sequence number and primary term
$retrievedDoc = $this->index->getDocument('1');
$this->assertTrue($retrievedDoc->hasSequenceNumber());
$this->assertTrue($retrievedDoc->hasPrimaryTerm());

// Update the document using the retrieved sequence number and primary term
$doc2 = new Document('1', ['title' => 'Updated document']);
$doc2->setSequenceNumber($retrievedDoc->getSequenceNumber());
$doc2->setPrimaryTerm($retrievedDoc->getPrimaryTerm());

$response2 = $this->index->addDocument($doc2);
$this->assertTrue($response2->isOk());

// Verify the document was updated
$updatedDoc = $this->index->getDocument('1');
$this->assertEquals('Updated document', $updatedDoc->get('title'));
}

/**
* @covers \Elastica\Index::addDocument
* @covers \Elastica\AbstractUpdateAction::setSequenceNumber
* @covers \Elastica\AbstractUpdateAction::setPrimaryTerm
* @group functional
*/
public function testAddDocumentWithStaleSeqNoPrimaryTerm(): void
{
// First, add a document
$doc1 = new Document('1', ['title' => 'Original document']);
$response1 = $this->index->addDocument($doc1);
$this->assertTrue($response1->isOk());

// Get the document to retrieve its sequence number and primary term
$retrievedDoc = $this->index->getDocument('1');
$originalSeqNo = $retrievedDoc->getSequenceNumber();
$originalPrimaryTerm = $retrievedDoc->getPrimaryTerm();

// Update the document once to change the sequence number
$doc2 = new Document('1', ['title' => 'First update']);
$doc2->setSequenceNumber($originalSeqNo);
$doc2->setPrimaryTerm($originalPrimaryTerm);
$response2 = $this->index->addDocument($doc2);
$this->assertTrue($response2->isOk());

// Try to update with the old sequence number and primary term (should fail)
$doc3 = new Document('1', ['title' => 'Second update']);
$doc3->setSequenceNumber($originalSeqNo);
$doc3->setPrimaryTerm($originalPrimaryTerm);

$response3 = $this->index->addDocument($doc3);
$this->assertFalse($response3->isOk());
}
}
96 changes: 96 additions & 0 deletions tests/SearchSeqNoPrimaryTermTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace Elastica\Test;

use Elastica\Document;
use Elastica\Index;
use Elastica\Search;
use Elastica\Test\Base as BaseTest;

/**
* @group functional
*
* @internal
*/
class SearchSeqNoPrimaryTermTest extends BaseTest
{
private Index $index;

protected function setUp(): void
{
parent::setUp();

$this->index = $this->_createIndex();
$this->index->addDocument(new Document('1', ['title' => 'Test document']));
$this->index->refresh();
}

protected function tearDown(): void
{
$this->index->delete();
parent::tearDown();
}

/**
* @covers \Elastica\Search::setOption
* @covers \Elastica\Search::OPTION_SEQ_NO_PRIMARY_TERM
* @group unit
*/
public function testSetSeqNoPrimaryTermOption(): void
{
$search = new Search($this->_getClient());
$search->addIndex($this->index);

$search->setOption(Search::OPTION_SEQ_NO_PRIMARY_TERM, true);

$this->assertTrue($search->hasOption(Search::OPTION_SEQ_NO_PRIMARY_TERM));
$this->assertTrue($search->getOption(Search::OPTION_SEQ_NO_PRIMARY_TERM));
}

/**
* @covers \Elastica\Search::search
* @covers \Elastica\Search::OPTION_SEQ_NO_PRIMARY_TERM
* @group functional
*/
public function testSearchWithSeqNoPrimaryTerm(): void
{
$search = new Search($this->_getClient());
$search->addIndex($this->index);
$search->setOption(Search::OPTION_SEQ_NO_PRIMARY_TERM, true);

$resultSet = $search->search();

$this->assertGreaterThan(0, $resultSet->count());

foreach ($resultSet as $result) {
$this->assertArrayHasKey('_seq_no', $result->getHit());
$this->assertArrayHasKey('_primary_term', $result->getHit());
$this->assertIsInt($result->getHit()['_seq_no']);
$this->assertIsInt($result->getHit()['_primary_term']);
}
}

/**
* @covers \Elastica\Search::setOptionsAndQuery
* @covers \Elastica\Search::OPTION_SEQ_NO_PRIMARY_TERM
* @group unit
*/
public function testSetOptionsAndQueryWithSeqNoPrimaryTerm(): void
{
$search = new Search($this->_getClient());
$search->addIndex($this->index);

$options = [
Search::OPTION_SEQ_NO_PRIMARY_TERM => true,
Search::OPTION_SIZE => 10,
];

$search->setOptionsAndQuery($options);

$this->assertTrue($search->hasOption(Search::OPTION_SEQ_NO_PRIMARY_TERM));
$this->assertTrue($search->getOption(Search::OPTION_SEQ_NO_PRIMARY_TERM));
$this->assertEquals(10, $search->getOption(Search::OPTION_SIZE));
}
}
Loading