packages/ztd-query-mysql/fuzz/Robustness/Target/RewriteTarget.php
1<?php
2
3declare(strict_types=1);
4
5namespace Fuzz\Robustness\Target;
6
7use Error;
8use Fuzz\Robustness\Invariant\ClassifyRewriteAgreementChecker;
9use Fuzz\Robustness\Invariant\RewritePlanConsistencyChecker;
10use ZtdQuery\Platform\MySql\Rewrite\MySqlQueryGuard;
11use ZtdQuery\Platform\MySql\Rewrite\MySqlRewriter;
12use ZtdQuery\Platform\MySql\Rewrite\Transformer\DeleteTransformer;
13use ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertTransformer;
14use ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlTransformer;
15use ZtdQuery\Platform\MySql\Rewrite\Transformer\ReplaceTransformer;
16use ZtdQuery\Platform\MySql\Rewrite\Transformer\SelectTransformer;
17use ZtdQuery\Platform\MySql\Rewrite\Transformer\UpdateTransformer;
18use ZtdQuery\Platform\MySql\Schema\MySqlSchemaParser;
19use ZtdQuery\Platform\MySql\Shadow\MySqlMutationResolver;
20use ZtdQuery\Platform\MySql\Sql\MySqlParser;
21use ZtdQuery\Schema\TableDefinitionRegistry;
22use ZtdQuery\Shadow\ShadowStore;
23
24/**
25 * Checks plan invariants and agreement between classification and rewriting.
26 */
27final class RewriteTarget
28{
29 /**
30 * Verify one statement with independent, disposable shadow state.
31 *
32 * @throws Error
33 */
34 public function __invoke(string $sql): void
35 {
36 $parser = new MySqlParser();
37 $schemaParser = new MySqlSchemaParser($parser);
38 $guard = new MySqlQueryGuard($parser);
39 $store = new ShadowStore();
40 $registry = new TableDefinitionRegistry();
41 $schemas = [
42 'users' => 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255), status VARCHAR(50))',
43 'orders' => 'CREATE TABLE orders (id INT PRIMARY KEY, user_id INT NOT NULL, amount DECIMAL(10,2), created_at DATETIME)',
44 'order_items' => 'CREATE TABLE order_items (order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, PRIMARY KEY (order_id, product_id))',
45 'products' => 'CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2), category VARCHAR(100))',
46 'events' => 'CREATE TABLE events (id INT PRIMARY KEY, event_date DATE) PARTITION BY RANGE (YEAR(event_date)) (PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION pmax VALUES LESS THAN MAXVALUE)',
47 ];
48
49 foreach ($schemas as $tableName => $createSql) {
50 $definition = $schemaParser->parse($createSql);
51 if ($definition !== null) {
52 $registry->register($tableName, $definition);
53 }
54 }
55 $rowsByTable = [
56 'users' => [
57 ['id' => '1', 'name' => 'Alice', 'email' => 'alice@example.com', 'status' => 'active'],
58 ['id' => '2', 'name' => 'Bob', 'email' => 'bob@example.com', 'status' => 'pending'],
59 ['id' => '3', 'name' => 'Charlie', 'email' => null, 'status' => 'active'],
60 ],
61 'orders' => [
62 ['id' => '1', 'user_id' => '1', 'amount' => '100.00', 'created_at' => '2024-01-01 00:00:00'],
63 ['id' => '2', 'user_id' => '2', 'amount' => '250.50', 'created_at' => '2024-01-02 12:30:00'],
64 ],
65 'order_items' => [
66 ['order_id' => '1', 'product_id' => '1', 'quantity' => '2'],
67 ['order_id' => '1', 'product_id' => '2', 'quantity' => '1'],
68 ['order_id' => '2', 'product_id' => '1', 'quantity' => '3'],
69 ],
70 'products' => [
71 ['id' => '1', 'name' => 'Widget', 'price' => '19.99', 'category' => 'tools'],
72 ['id' => '2', 'name' => 'Gadget', 'price' => '49.99', 'category' => 'electronics'],
73 ],
74 'events' => [
75 ['id' => '1', 'event_date' => '2023-06-01'],
76 ['id' => '2', 'event_date' => '2024-06-01'],
77 ],
78 ];
79 foreach ($rowsByTable as $table => $rows) {
80 $store->set($table, $rows);
81 }
82 $select = new SelectTransformer();
83 $update = new UpdateTransformer($parser, $select);
84 $delete = new DeleteTransformer($parser, $select);
85 $transformer = new MySqlTransformer($parser, $select, new InsertTransformer($parser, $select), $update, $delete, new ReplaceTransformer($parser, $select));
86 $mutationResolver = new MySqlMutationResolver($store, $registry, $schemaParser, $update, $delete);
87 $rewriter = new MySqlRewriter($guard, $store, $registry, $transformer, $mutationResolver, $parser);
88 try {
89 $checks = [
90 new RewritePlanConsistencyChecker($rewriter),
91 new ClassifyRewriteAgreementChecker($guard, $rewriter),
92 ];
93 foreach ($checks as $check) {
94 $violation = $check->check($sql);
95 if ($violation !== null) {
96 throw new Error((string) $violation);
97 }
98 }
99 } finally {
100 $store->clear();
101 foreach ($registry->getAll() as $name => $definition) {
102 $registry->unregister($name);
103 }
104 }
105 }
106}
107