packages/ztd-query-mysql/fuzz/Robustness/Target/RobustnessTarget.php
1<?php
2
3declare(strict_types=1);
4
5namespace Fuzz\Robustness\Target;
6
7use Error;
8use Fuzz\Robustness\Invariant\ShadowStoreConsistencyChecker;
9use ZtdQuery\Exception\SimulationException;
10use ZtdQuery\Exception\UnknownSchemaException;
11use ZtdQuery\Exception\UnsupportedSqlException;
12use ZtdQuery\Platform\MySql\Rewrite\MySqlQueryGuard;
13use ZtdQuery\Platform\MySql\Rewrite\MySqlRewriter;
14use ZtdQuery\Platform\MySql\Rewrite\Transformer\DeleteTransformer;
15use ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertTransformer;
16use ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlTransformer;
17use ZtdQuery\Platform\MySql\Rewrite\Transformer\ReplaceTransformer;
18use ZtdQuery\Platform\MySql\Rewrite\Transformer\SelectTransformer;
19use ZtdQuery\Platform\MySql\Rewrite\Transformer\UpdateTransformer;
20use ZtdQuery\Platform\MySql\Schema\MySqlSchemaParser;
21use ZtdQuery\Platform\MySql\Shadow\MySqlMutationResolver;
22use ZtdQuery\Platform\MySql\Sql\MySqlParser;
23use ZtdQuery\Schema\TableDefinitionRegistry;
24use ZtdQuery\Shadow\ShadowStore;
25
26/**
27 * Exercises classification, rewrite invariants and simulated writes in isolated state.
28 */
29final class RobustnessTarget
30{
31 /**
32 * Apply metadata mutations and verify store structure after every outcome.
33 *
34 * @throws Error
35 */
36 public function __invoke(string $sql): void
37 {
38 (new ClassifyTarget())($sql);
39 (new RewriteTarget())($sql);
40 $parser = new MySqlParser();
41 $schemaParser = new MySqlSchemaParser($parser);
42 $guard = new MySqlQueryGuard($parser);
43 $store = new ShadowStore();
44 $registry = new TableDefinitionRegistry();
45 $schemas = [
46 'users' => 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255), status VARCHAR(50))',
47 'orders' => 'CREATE TABLE orders (id INT PRIMARY KEY, user_id INT NOT NULL, amount DECIMAL(10,2), created_at DATETIME)',
48 '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))',
49 'products' => 'CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2), category VARCHAR(100))',
50 '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)',
51 ];
52
53 foreach ($schemas as $tableName => $createSql) {
54 $definition = $schemaParser->parse($createSql);
55 if ($definition !== null) {
56 $registry->register($tableName, $definition);
57 }
58 }
59 $rowsByTable = [
60 'users' => [
61 ['id' => '1', 'name' => 'Alice', 'email' => 'alice@example.com', 'status' => 'active'],
62 ['id' => '2', 'name' => 'Bob', 'email' => 'bob@example.com', 'status' => 'pending'],
63 ['id' => '3', 'name' => 'Charlie', 'email' => null, 'status' => 'active'],
64 ],
65 'orders' => [
66 ['id' => '1', 'user_id' => '1', 'amount' => '100.00', 'created_at' => '2024-01-01 00:00:00'],
67 ['id' => '2', 'user_id' => '2', 'amount' => '250.50', 'created_at' => '2024-01-02 12:30:00'],
68 ],
69 'order_items' => [
70 ['order_id' => '1', 'product_id' => '1', 'quantity' => '2'],
71 ['order_id' => '1', 'product_id' => '2', 'quantity' => '1'],
72 ['order_id' => '2', 'product_id' => '1', 'quantity' => '3'],
73 ],
74 'products' => [
75 ['id' => '1', 'name' => 'Widget', 'price' => '19.99', 'category' => 'tools'],
76 ['id' => '2', 'name' => 'Gadget', 'price' => '49.99', 'category' => 'electronics'],
77 ],
78 'events' => [
79 ['id' => '1', 'event_date' => '2023-06-01'],
80 ['id' => '2', 'event_date' => '2024-06-01'],
81 ],
82 ];
83 foreach ($rowsByTable as $table => $rows) {
84 $store->set($table, $rows);
85 }
86 $select = new SelectTransformer();
87 $update = new UpdateTransformer($parser, $select);
88 $delete = new DeleteTransformer($parser, $select);
89 $transformer = new MySqlTransformer($parser, $select, new InsertTransformer($parser, $select), $update, $delete, new ReplaceTransformer($parser, $select));
90 $mutationResolver = new MySqlMutationResolver($store, $registry, $schemaParser, $update, $delete);
91 $rewriter = new MySqlRewriter($guard, $store, $registry, $transformer, $mutationResolver, $parser);
92 try {
93 try {
94 $plan = $rewriter->rewrite($sql);
95 } catch (UnsupportedSqlException | UnknownSchemaException) {
96 return;
97 }
98 $mutation = $plan->mutation();
99 if ($mutation === null) {
100 return;
101 }
102 try {
103 /**
104 * @throws SimulationException Domain rejection declared by concrete shadow mutations.
105 */
106 $mutation->apply($store, []);
107 } catch (SimulationException) {
108 return;
109 } finally {
110 $violation = (new ShadowStoreConsistencyChecker($store))->check($sql);
111 if ($violation !== null) {
112 throw new Error((string) $violation);
113 }
114 }
115 } finally {
116 $store->clear();
117 foreach ($registry->getAll() as $name => $definition) {
118 $registry->unregister($name);
119 }
120 }
121 }
122}
123