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\MySql80Container;
8use Container\MySql84Container;
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\Session;
29use ZtdQuery\Shadow\Mutation\InsertMutation;
30use ZtdQuery\Shadow\Mutation\ShadowMutation;
31use ZtdQuery\Shadow\ShadowStore;
32
33#[CoversClass(MysqliResultProcessor::class)]
34#[UsesClass(MysqliResultStatement::class)]
35#[UsesClass(MysqliResultColumnExtractor::class)]
36#[UsesClass(ZtdMysqliException::class)]
37#[Large]
38final class MysqliResultProcessorTest extends TestCase
39{
40 public function testProcessCreatesAnEmptyWriteResult(): void
41 {
42 $store = new ShadowStore();
43 $session = new Session(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
44 $plan = new RewritePlan('DO 1', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
45 $result = (new MysqliResultProcessor())->process($session, $plan, false, 0);
46 self::assertTrue($result->isSuccess());
47 self::assertSame(0, $result->rowCount());
48 self::assertSame([], $result->fetchAll());
49 }
50
51 public function testProcessUsesNativeRowsForTheShadowMutation(): void
52 {
53 $container = Testcontainers::run(getenv('MYSQL_VERSION') === '8.4.7' ? MySql84Container::class : MySql80Container::class);
54 try {
55 $connection = new mysqli(str_replace('localhost', '127.0.0.1', $container->getHost()), 'root', 'root', 'test', $container->getMappedPort(3306));
56 $connection->set_charset('utf8mb4');
57 $store = new ShadowStore();
58 $session = new Session(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($session, $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(getenv('MYSQL_VERSION') === '8.4.7' ? MySql84Container::class : MySql80Container::class);
75 try {
76 $connection = new mysqli(str_replace('localhost', '127.0.0.1', $container->getHost()), 'root', 'root', 'test', $container->getMappedPort(3306));
77 $connection->set_charset('utf8mb4');
78 $store = new ShadowStore();
79 $session = new Session(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
80 $result = $connection->query('SELECT 7 AS id');
81 self::assertInstanceOf(mysqli_result::class, $result);
82 $mutation = self::createStub(ShadowMutation::class);
83 $failure = new DatabaseException('result processing failed', 123, 123);
84 $mutation->method('apply')->willThrowException($failure);
85 $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, $mutation);
86 try {
87 (new MysqliResultProcessor())->process($session, $plan, $result, 1);
88 self::fail('Expected the MySQLi exception.');
89 } catch (ZtdMysqliException $exception) {
90 self::assertSame($failure, $exception->getPrevious());
91 self::assertSame('result processing failed', $exception->getMessage());
92 self::assertSame(0, $exception->getCode());
93 } finally {
94 $connection->close();
95 }
96 } finally {
97 $container->stop();
98 }
99 }
100
101}
102