packages/ztd-query-mysqli-adapter/bench/ResultMetadataBench.php
1<?php
2
3declare(strict_types=1);
4
5namespace Bench;
6
7use Container\Endpoint;
8use Container\MySqlRelease;
9use mysqli;
10use mysqli_result;
11use PhpBench\Attributes as Bench;
12use RuntimeException;
13use Testcontainers\Testcontainers;
14use ZtdQuery\Adapter\Mysqli\Driver\MysqliResultColumnExtractor;
15use ZtdQuery\Platform\MySql\MySqlResultColumnTypeResolver;
16
17/**
18 * Measures metadata adaptation independently of database round trips.
19 */
20#[Bench\BeforeMethods('setUp')]
21#[Bench\AfterMethods('tearDown')]
22#[Bench\ParamProviders('columns')]
23#[Bench\Revs(10000)]
24final class ResultMetadataBench
25{
26 private mysqli $connection;
27 private mysqli_result $result;
28 private MySqlResultColumnTypeResolver $resolver;
29
30 /**
31 * Prepare one native result; setup and cleanup are outside measured work.
32 *
33 * @param array{columns: int} $params
34 * @throws RuntimeException If the native SELECT does not produce metadata.
35 */
36 public function setUp(array $params): void
37 {
38 $container = Testcontainers::run(MySqlRelease::fromEnvironment());
39 $endpoint = $container->getData(Endpoint::class);
40 $this->connection = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
41 $this->connection->set_charset('utf8mb4');
42 $projection = [];
43 for ($column = 0; $column < $params['columns']; $column++) {
44 $projection[] = $column . ' AS column_' . $column;
45 }
46 $result = $this->connection->query('SELECT ' . implode(', ', $projection));
47 if (!$result instanceof mysqli_result) {
48 throw new RuntimeException('Benchmark SELECT did not return a result.');
49 }
50 $this->result = $result;
51 $this->resolver = new MySqlResultColumnTypeResolver();
52 }
53
54 /**
55 * Release native resources after timing has finished.
56 */
57 public function tearDown(): void
58 {
59 $this->result->free();
60 $this->connection->close();
61 Testcontainers::stop();
62 }
63
64 /**
65 * Exercise narrow, ordinary and wide query projections.
66 *
67 * @return iterable<string, array{columns: int}>
68 */
69 public function columns(): iterable
70 {
71 yield 'one-column' => ['columns' => 1];
72 yield 'sixteen-columns' => ['columns' => 16];
73 yield 'sixty-four-columns' => ['columns' => 64];
74 }
75
76 /**
77 * Convert field metadata using the same resolver as the production adapter.
78 */
79 public function benchExtract(): void
80 {
81 MysqliResultColumnExtractor::extract($this->result, $this->resolver);
82 }
83}
84