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