packages/ztd-query-mysqli-adapter/tests/Unit/Session/MysqliResultProcessorTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Session;
6
7use Container\Endpoint;
8use Container\MySqlRelease;
9use mysqli;
10use mysqli_result;
11use PHPUnit\Framework\Attributes\CoversClass;
12use PHPUnit\Framework\Attributes\Large;
13use PHPUnit\Framework\Attributes\UsesClass;
14use PHPUnit\Framework\TestCase;
15use Testcontainers\Testcontainers;
16use ZtdQuery\Adapter\Mysqli\Driver\MysqliResultColumnExtractor;
17use ZtdQuery\Adapter\Mysqli\Driver\MysqliResultStatement;
18use ZtdQuery\Adapter\Mysqli\Session\MysqliResultProcessor;
19use ZtdQuery\Adapter\Mysqli\ZtdMysqliException;
20use ZtdQuery\Config\ZtdConfig;
21use ZtdQuery\Connection\ConnectionInterface;
22use ZtdQuery\Connection\Exception\DatabaseException;
23use ZtdQuery\Platform\MySql\MySqlResultColumnTypeResolver;
24use ZtdQuery\ResultSelectRunner;
25use ZtdQuery\Rewrite\QueryKind;
26use ZtdQuery\Rewrite\RewritePlan;
27use ZtdQuery\Rewrite\SqlRewriter;
28use ZtdQuery\Shadow\Mutation\InsertMutation;
29use ZtdQuery\Shadow\Mutation\ShadowMutation;
30use ZtdQuery\Shadow\ShadowStore;
31
32#[CoversClass(MysqliResultProcessor::class)]
33#[UsesClass(MysqliResultStatement::class)]
34#[UsesClass(MysqliResultColumnExtractor::class)]
35#[UsesClass(ZtdMysqliException::class)]
36#[Large]
37final class MysqliResultProcessorTest extends TestCase
38{
39 public function testProcessCreatesAnEmptyWriteResult(): void
40 {
41 $store = new ShadowStore();
42 $executor = \Tests\Fake\QueryExecutorBuilder::create(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
43 $plan = new RewritePlan('DO 1', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
44 $result = (new MysqliResultProcessor())->process($executor, $plan, false, 0);
45 self::assertTrue($result->isSuccess());
46 self::assertSame(0, $result->rowCount());
47 self::assertSame([], $result->fetchAll());
48 }
49
50 public function testProcessUsesNativeRowsForTheShadowMutation(): void
51 {
52 $container = Testcontainers::run(MySqlRelease::fromEnvironment());
53 $endpoint = $container->getData(Endpoint::class);
54 try {
55 $connection = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
56 $connection->set_charset('utf8mb4');
57 $store = new ShadowStore();
58 $executor = \Tests\Fake\QueryExecutorBuilder::create(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
59 $result = $connection->query('SELECT 7 AS id');
60 self::assertInstanceOf(mysqli_result::class, $result);
61 $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
62 $processed = (new MysqliResultProcessor())->process($executor, $plan, $result, 1);
63 self::assertTrue($processed->isSuccess());
64 self::assertSame(1, $processed->rowCount());
65 self::assertSame([['id' => '7']], $store->get('items'));
66 $connection->close();
67 } finally {
68 $container->stop();
69 }
70 }
71
72 public function testProcessPreservesTheOriginalDatabaseException(): void
73 {
74 $container = Testcontainers::run(MySqlRelease::fromEnvironment());
75 $endpoint = $container->getData(Endpoint::class);
76 try {
77 $connection = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
78 $connection->set_charset('utf8mb4');
79 $store = new ShadowStore();
80 $executor = \Tests\Fake\QueryExecutorBuilder::create(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
81 $result = $connection->query('SELECT 7 AS id');
82 self::assertInstanceOf(mysqli_result::class, $result);
83 $mutation = self::createStub(ShadowMutation::class);
84 $failure = new DatabaseException('result processing failed', 123, 123);
85 $mutation->method('apply')->willThrowException($failure);
86 $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, $mutation);
87 try {
88 (new MysqliResultProcessor())->process($executor, $plan, $result, 1);
89 self::fail('Expected the MySQLi exception.');
90 } catch (ZtdMysqliException $exception) {
91 self::assertSame($failure, $exception->getPrevious());
92 self::assertSame('result processing failed', $exception->getMessage());
93 self::assertSame(0, $exception->getCode());
94 } finally {
95 $connection->close();
96 }
97 } finally {
98 $container->stop();
99 }
100 }
101
102}
103