* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use League\Csv\Query\Constraint\Comparison; use League\Csv\Query\QueryException; use OutOfBoundsException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use SplTempFileObject; use function array_reverse; use function in_array; use function strcmp; use function strlen; #[Group('reader')] final class StatementTest extends TestCase { private Reader $csv; private Statement $stmt; private array $expected = [ ['john', 'doe', 'john.doe@example.com'], ['jane', 'doe', 'jane.doe@example.com'], ]; protected function setUp(): void { $tmp = new SplTempFileObject(); $tmp->setCsvControl(escape: '\\'); foreach ($this->expected as $row) { $tmp->fputcsv($row, escape: '\\'); } $this->csv = Reader::from($tmp); $this->stmt = (new Statement()); } protected function tearDown(): void { unset($this->csv, $this->stmt); } public function testSetLimit(): void { self::assertCount(1, $this->stmt->limit(1)->process($this->csv)); } public function testSetLimitThrowException(): void { $this->expectException(InvalidArgument::class); $this->stmt->limit(-2); } public function testSetOffset(): void { self::assertContains( ['jane', 'doe', 'jane.doe@example.com'], [...$this->stmt->offset(1)->process($this->csv)] ); } public function testSetOffsetThrowsException(): void { $this->expectException(InvalidArgument::class); $this->stmt->offset(-1); } public function testStatementSameInstance(): void { self::assertSame($this->stmt, $this->stmt->limit(-1)->offset(0)); } #[DataProvider('intervalTest')] public function testInterval(int $offset, int $limit): void { self::assertContains( ['jane', 'doe', 'jane.doe@example.com'], [...$this->stmt ->offset($offset) ->limit($limit) ->where(fn (array $record): bool => true) ->where(fn (array $record): bool => [] !== $record) ->process($this->csv)] ); } public static function intervalTest(): array { return [ 'tooHigh' => [1, 10], 'normal' => [1, 1], ]; } public function testIntervalThrowException(): void { $this->expectException(OutOfBoundsException::class); [...$this->stmt->offset(1)->limit(0)->process($this->csv)]; } public function testFilter(): void { $func2 = fn (array $row): bool => !in_array('john', $row, true); $stmt = (new Statement())->where(fn (array $row): bool => !in_array('jane', $row, true)); $result1 = $stmt->process($this->csv); $result2 = $stmt->where($func2)->process($result1, ['foo', 'bar']); $result3 = $stmt->where($func2)->process($result2, ['foo', 'bar']); self::assertNotContains(['jane', 'doe', 'jane.doe@example.com'], [...$result1]); self::assertCount(0, $result2); self::assertEquals($result3, $result2); } public function testAddWhere(): void { $stmt1 = (new Statement())->andWhere(0, '=', 'jane'); $result1 = $stmt1->process($this->csv); self::assertCount(1, $result1); self::assertEquals('jane', $result1->first()[0]); $stmt2 = (new Statement())->andWhere(0, 'starts_with', 'j'); $result2 = $stmt2->process($this->csv); self::assertCount(2, $result2); self::assertEquals('jane', $result2->nth(1)[0]); $stmt3 = (new Statement())->andWhere(2, 'starts_with', 'blablabla'); $result3 = $stmt3->process($this->csv); self::assertCount(0, $result3); } public function testOrWhere(): void { $stmt1 = (new Statement()) ->orWhere(0, 'starts_with', 'ja') ->orWhere(0, 'ends_with', 'hn'); $result1 = $stmt1->process($this->csv); self::assertCount(2, $result1); self::assertEquals('john', $result1->first()[0]); $stmt3 = (new Statement())->orWhere(2, 'starts_with', 'blablabla'); $result3 = $stmt3->process($this->csv); self::assertCount(0, $result3); } public function testOrderBy(): void { $calculated = $this->stmt ->orderBy(fn (array $rowA, array $rowB): int => strcmp($rowA[0], $rowB[0])) /* @phpstan-ignore-line */ ->process($this->csv); self::assertSame(array_reverse($this->expected), array_values([...$calculated])); } public function testOrderByColumn(): void { $calculated = $this->stmt ->orderByAsc(0) ->process($this->csv); self::assertSame(array_reverse($this->expected), array_values([...$calculated])); $calculated = $this->stmt ->orderByDesc(0) ->process($this->csv); self::assertSame($this->expected, array_values([...$calculated])); } public function testOrderByColumnThrowsExceptionIfTheOffsetDoesNotExists(): void { $this->expectException(QueryException::class); $this->stmt->orderByDesc(-42)->process($this->csv); } public function testOrderByWithEquity(): void { $calculated = $this->stmt ->orderBy(fn (array $rowA, array $rowB): int => strlen($rowA[0]) <=> strlen($rowB[0])) /* @phpstan-ignore-line */ ->process($this->csv); self::assertSame($this->expected, array_values([...$calculated])); } public function testHeaderMapperOnStatement(): void { $results = (new Statement()) ->process($this->csv, [2 => 'e-mail', 1 => 'lastname', 33 => 'does not exists']); self::assertSame(['e-mail', 'lastname', 'does not exists'], $results->getHeader()); self::assertSame([ 'e-mail' => 'john.doe@example.com', 'lastname' => 'doe', 'does not exists' => null, ], $results->first()); } public function testOrderByDoesNotThrowOnInvalidOffsetOrLimit(): void { $document = <<setHeaderOffset(0); $constraints = (new Statement()) ->select('Integer', 'Text', 'Date and Time') ->where(fn (array $record): bool => (float) $record['Float'] < 1.3) ->orderBy(fn (array $record1, array $record2): int => (int) $record2['Integer'] <=> (int) $record1['Integer']) /* @phpstan-ignore-line */ ->limit(5) ->offset(2); self::assertSame([], $constraints->process($csv)->nth(42)); } public function testItCanCompareNullValue(): void { $document = <<setHeaderOffset(0); $statement = (new Statement()) ->andWhere('Number', Comparison::Contains, '117'); self::assertCount(1, $statement->process($csv)); } public function testselectAllExcept(): void { $document = <<setHeaderOffset(0); $statement = (new Statement()) ->limit(1) ->selectAllExcept('Number'); self::assertSame(['Title' => 'Commander', 'Name' => 'Fred'], $statement->process($csv)->first()); } }