packages/sql-semantics-mysql/fuzz/Target/RoundTripTarget.php

1<?php
2
3declare(strict_types=1);
4
5namespace Fuzz\Target;
6
7use Error;
8use SqlFormatter\Facade\Formatter;
9use SqlSemantics\Facade\Semantics;
10use Throwable;
11
12/**
13 * Every generated statement must be represented and written using its semantic data.
14 */
15final class RoundTripTarget
16{
17    public function __construct(
18        private readonly Semantics $semantics,
19        private readonly Formatter $compact,
20        private readonly string $grammarVersion,
21    ) {
22    }
23
24    /**
25     * Records rejections, serialization failures and differences as fuzz findings.
26     */
27    public function verify(string $sql, string $input): void
28    {
29        $context = "Grammar: {$this->grammarVersion}\nInput (hex): " . bin2hex($input) . "\nSQL: {$sql}";
30        if ($sql === '') {
31            throw new Error("Statement generation returned an empty string\n{$context}");
32        }
33        $printed = null;
34        try {
35            $statement = $this->semantics->analyze($sql);
36            $printed = $statement->toString();
37            $expected = $this->compact->format($sql);
38            $actual = $this->compact->format($printed);
39        } catch (Throwable $failure) {
40            throw new Error("Semantic round trip failed\n{$context}\nPrinted: {$printed}\nError: {$failure->getMessage()}", 0, $failure);
41        }
42        if ($actual !== $expected) {
43            throw new Error("Semantic round trip changed the statement\n{$context}\nPrinted: {$printed}\nExpected: {$expected}\nActual: {$actual}");
44        }
45    }
46}
47