Skip to content

Commit

Permalink
Add IterableEntityIdPager
Browse files Browse the repository at this point in the history
This can be used to page through an iterable of entity IDs without
having to load it into memory all at once, as an alternative to:

    new InMemoryEntityIdPager( ...$iterable )
  • Loading branch information
lucaswerkmeister committed Apr 7, 2020
1 parent fceb49c commit 7dcf2a9
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Wikibase DataModel Services release notes

## Version 5.3.0 (dev)
* Added `IterableEntityIdPager`

## Version 5.2.0 (2020-03-10)
* Allow installing with wikimedia/assert 0.5.0

Expand Down
52 changes: 52 additions & 0 deletions src/EntityId/IterableEntityIdPager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Wikibase\DataModel\Services\EntityId;

use ArrayIterator;
use Iterator;
use IteratorIterator;
use Wikibase\DataModel\Entity\EntityId;

/**
* An entity ID pager that wraps an iterable and traverses it once.
* It is not seekable or rewindable.
*
* @since 5.3
* @license GPL-2.0-or-later
*/
class IterableEntityIdPager implements EntityIdPager {

/** @var Iterator */
private $iterator;

/**
* @param iterable<EntityId> $iterable
*/
public function __construct( iterable $iterable ) {
if ( $iterable instanceof Iterator ) {
$this->iterator = $iterable;
} elseif ( is_array( $iterable ) ) {
$this->iterator = new ArrayIterator( $iterable );
} else {
$this->iterator = new IteratorIterator( $iterable );
}
$this->iterator->rewind();
}

/**
* @see EntityIdPager::fetchIds
*
* @param int $limit
*
* @return EntityId[]
*/
public function fetchIds( $limit ) {
$ids = [];
while ( $limit-- > 0 && $this->iterator->valid() ) {
$ids[] = $this->iterator->current();
$this->iterator->next();
}
return $ids;
}

}
99 changes: 99 additions & 0 deletions tests/unit/EntityId/IterableEntityIdPagerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace Wikibase\DataModel\Services\Tests\EntityId;

use ArrayIterator;
use IteratorAggregate;
use PHPUnit\Framework\TestCase;
use Wikibase\DataModel\Services\EntityId\IterableEntityIdPager;

/**
* Note: this test has the pager iterate through numbers, not entity IDs.
* It simplifies the test, and the pager doesn’t care.
*
* @covers \Wikibase\DataModel\Services\EntityId\IterableEntityIdPager
*
* @license GPL-2.0-or-later
*/
class IterableEntityIdPagerTest extends TestCase {

private const ONE_THROUGH_TEN = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

private function yieldOneThroughTenIndividually() {
foreach ( self::ONE_THROUGH_TEN as $number ) {
yield $number;
}
}

private function yieldOneThroughTenAsYieldFrom() {
yield from self::ONE_THROUGH_TEN;
}

/** Various iterables which all yield the numbers one through ten (both inclusive). */
public function provideIterables() {
yield 'array' => [ self::ONE_THROUGH_TEN ];
yield 'ArrayIterator' => [ new ArrayIterator( self::ONE_THROUGH_TEN ) ];
yield 'generator I' => [ $this->yieldOneThroughTenIndividually() ];
yield 'generator II' => [ $this->yieldOneThroughTenAsYieldFrom() ];
$aggregate = $this->createMock( IteratorAggregate::class );
$aggregate->method( 'getIterator' )->willReturn( new ArrayIterator( self::ONE_THROUGH_TEN ) );
yield 'IteratorAggregate' => [ $aggregate ];
}

public function providePagers() {
foreach ( $this->provideIterables() as $key => $iterable ) {
yield $key => [ new IterableEntityIdPager( $iterable[0] ) ];
}
}

/** @dataProvider providePagers */
public function testOneBatchLimit10( IterableEntityIdPager $pager ) {
$this->assertSame( self::ONE_THROUGH_TEN, $pager->fetchIds( 10 ) );
$this->assertSame( [], $pager->fetchIds( 1 ) );
$this->assertSame( [], $pager->fetchIds( 10 ) );
}

/** @dataProvider providePagers */
public function testOneBatchLimit100( IterableEntityIdPager $pager ) {
$this->assertSame( self::ONE_THROUGH_TEN, $pager->fetchIds( 100 ) );
$this->assertSame( [], $pager->fetchIds( 1 ) );
$this->assertSame( [], $pager->fetchIds( 10 ) );
}

/** @dataProvider providePagers */
public function testTwoBatchesLimits5And5( IterableEntityIdPager $pager ) {
$this->assertSame( [ 1, 2, 3, 4, 5 ], $pager->fetchIds( 5 ) );
$this->assertSame( [ 6, 7, 8, 9, 10 ], $pager->fetchIds( 5 ) );
$this->assertSame( [], $pager->fetchIds( 5 ) );
$this->assertSame( [], $pager->fetchIds( 1 ) );
$this->assertSame( [], $pager->fetchIds( 0 ) );
}

/** @dataProvider providePagers */
public function testThreeBatchesLimits0And5And5( IterableEntityIdPager $pager ) {
$this->assertSame( [], $pager->fetchIds( 0 ) );
$this->assertSame( [ 1, 2, 3, 4, 5 ], $pager->fetchIds( 5 ) );
$this->assertSame( [ 6, 7, 8, 9, 10 ], $pager->fetchIds( 5 ) );
$this->assertSame( [], $pager->fetchIds( 5 ) );
$this->assertSame( [], $pager->fetchIds( 1 ) );
$this->assertSame( [], $pager->fetchIds( 0 ) );
}

/** @dataProvider providePagers */
public function testFourBatchesLimits1And2And3And4( IterableEntityIdPager $pager ) {
$this->assertSame( [ 1 ], $pager->fetchIds( 1 ) );
$this->assertSame( [ 2, 3 ], $pager->fetchIds( 2 ) );
$this->assertSame( [ 4, 5, 6 ], $pager->fetchIds( 3 ) );
$this->assertSame( [ 7, 8, 9, 10 ], $pager->fetchIds( 4 ) );
$this->assertSame( [], $pager->fetchIds( 5 ) );
}

/** @dataProvider providePagers */
public function testTenBatchesEachLimit1( IterableEntityIdPager $pager ) {
foreach ( self::ONE_THROUGH_TEN as $i ) {
$this->assertSame( [ $i ], $pager->fetchIds( 1 ) );
}
$this->assertSame( [], $pager->fetchIds( 1 ) );
}

}

0 comments on commit 7dcf2a9

Please sign in to comment.