packages/ztd-query-mysql/fuzz/Robustness/Invariant/ClassifyRewriteAgreementChecker.php
1<?php
2
3declare(strict_types=1);
4
5namespace Fuzz\Robustness\Invariant;
6
7use ZtdQuery\Exception\UnknownSchemaException;
8use ZtdQuery\Exception\UnsupportedSqlException;
9use ZtdQuery\Platform\MySql\Rewrite\MySqlQueryGuard;
10use ZtdQuery\Platform\MySql\Rewrite\MySqlRewriter;
11use ZtdQuery\Platform\MySql\Sql\Diagnostic\MySqlReadOnlyDiagnosticStatement;
12use ZtdQuery\Rewrite\QueryKind;
13
14/**
15 * Implements the Classify Rewrite Agreement Checker contract for MySQL.
16 */
17final class ClassifyRewriteAgreementChecker implements InvariantChecker
18{
19 private MySqlQueryGuard $guard;
20 private MySqlRewriter $rewriter;
21
22 /**
23 * Configure the dependencies used by this operation.
24 */
25 public function __construct(MySqlQueryGuard $guard, MySqlRewriter $rewriter)
26 {
27 $this->guard = $guard;
28 $this->rewriter = $rewriter;
29 }
30
31 /**
32 * Check for the supplied MySQL input.
33 */
34 public function check(string $sql): ?InvariantViolation
35 {
36 $diagnostic = MySqlReadOnlyDiagnosticStatement::isSafe($sql);
37
38 $classifyResult = $this->guard->classify($sql);
39
40 if ($diagnostic && $classifyResult !== QueryKind::READ) {
41 return new InvariantViolation('INV-L2-06', 'read-only diagnostic was not classified as READ', $sql);
42 }
43
44 try {
45 $plan = $this->rewriter->rewrite($sql);
46 } catch (UnknownSchemaException $exception) {
47 if ($diagnostic) {
48 return new InvariantViolation('INV-L2-06', 'read-only diagnostic required schema metadata', $sql, ['exception' => $exception::class]);
49 }
50
51 return null;
52 } catch (UnsupportedSqlException $exception) {
53 if ($diagnostic) {
54 return new InvariantViolation('INV-L2-06', 'read-only diagnostic was rejected', $sql, ['exception' => $exception::class]);
55 }
56
57 return null;
58 }
59
60 if ($diagnostic && ($plan->kind() !== QueryKind::READ || $plan->sql() !== $sql)) {
61 return new InvariantViolation('INV-L2-06', 'read-only diagnostic was not preserved as an unchanged READ plan', $sql);
62 }
63
64 if ($plan->kind() !== $classifyResult) {
65 return new InvariantViolation(
66 'INV-L2-05',
67 'classify() and rewrite() disagree on QueryKind',
68 $sql,
69 [
70 'classify_result' => $classifyResult?->value,
71 'rewrite_kind' => $plan->kind()->value,
72 ]
73 );
74 }
75
76 return null;
77 }
78}
79