packages/ztd-query-core/tests/Unit/Shadow/ShadowTransactionsTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Schema\TableDefinition;
11use ZtdQuery\Schema\TableDefinitionRegistry;
12use ZtdQuery\Shadow\ShadowStore;
13use ZtdQuery\Shadow\ShadowTransactions;
14
15#[CoversClass(ShadowTransactions::class)]
16#[UsesClass(ShadowStore::class)]
17#[UsesClass(TableDefinition::class)]
18#[UsesClass(TableDefinitionRegistry::class)]
19#[UsesClass(\ZtdQuery\Shadow\ShadowSavepoint::class)]
20#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
21final class ShadowTransactionsTest extends TestCase
22{
23 public function testRollBackPutsBackTheRowsTheTablesAndWhatDescribesThem(): void
24 {
25 $store = new ShadowStore();
26 $store->set('users', [['id' => 1]]);
27 $registry = new TableDefinitionRegistry();
28 $definition = new TableDefinition(['id'], ['id' => 'INT'], ['id'], ['id'], []);
29 $registry->register('users', $definition);
30 $transactions = new ShadowTransactions($store, $registry);
31
32 $transactions->begin();
33 $store->insert('users', [['id' => 2]]);
34 $store->set('temporary_table', []);
35 $registry->unregister('users');
36 $transactions->rollBack();
37
38 self::assertSame([['id' => 1]], $store->get('users'));
39 self::assertFalse($store->has('temporary_table'));
40 self::assertSame($definition, $registry->get('users'));
41 }
42
43 public function testRollBackToKeepsTheSavepointAndGivesUpEveryLaterOne(): void
44 {
45 $store = new ShadowStore();
46 $store->set('items', [['id' => 1]]);
47 $transactions = new ShadowTransactions($store);
48
49 $transactions->begin();
50 $store->insert('items', [['id' => 2]]);
51 $transactions->savepoint('outer');
52 $store->insert('items', [['id' => 3]]);
53 $transactions->savepoint('inner');
54 $store->insert('items', [['id' => 4]]);
55 $transactions->rollBackTo('outer');
56 $store->insert('items', [['id' => 5]]);
57 $transactions->commit();
58
59 self::assertSame([['id' => 1], ['id' => 2], ['id' => 5]], $store->get('items'));
60 }
61
62 public function testReleaseGivesUpTheSavepointAndEveryNestedOneWithoutUndoingAnything(): void
63 {
64 $store = new ShadowStore();
65 $store->set('items', [['id' => 1]]);
66 $transactions = new ShadowTransactions($store);
67
68 $transactions->begin();
69 $transactions->savepoint('outer');
70 $store->insert('items', [['id' => 2]]);
71 $transactions->savepoint('inner');
72 $transactions->release('outer');
73 $transactions->rollBackTo('inner');
74 $transactions->commit();
75
76 self::assertSame([['id' => 1], ['id' => 2]], $store->get('items'));
77 }
78
79 public function testSavepointDeclaredTwiceUnderOneNameTakesThePlaceOfTheFirst(): void
80 {
81 $store = new ShadowStore();
82 $store->set('items', [['id' => 1]]);
83 $transactions = new ShadowTransactions($store);
84
85 $transactions->begin();
86 $transactions->savepoint('point');
87 $store->insert('items', [['id' => 2]]);
88 $transactions->savepoint('point');
89 $store->insert('items', [['id' => 3]]);
90 $transactions->rollBackTo('point');
91
92 self::assertSame([['id' => 1], ['id' => 2]], $store->get('items'));
93 $transactions->release('point');
94 $store->insert('items', [['id' => 4]]);
95 $transactions->rollBackTo('point');
96 self::assertSame([['id' => 1], ['id' => 2], ['id' => 4]], $store->get('items'));
97 }
98
99 public function testRollBackToLeavesItsTargetToBeRolledBackToAgain(): void
100 {
101 $store = new ShadowStore();
102 $store->set('items', [['id' => 1]]);
103 $transactions = new ShadowTransactions($store);
104
105 $transactions->begin();
106 $store->insert('items', [['id' => 2]]);
107 $transactions->savepoint('outer');
108 $store->insert('items', [['id' => 3]]);
109 $transactions->savepoint('inner');
110 $store->insert('items', [['id' => 4]]);
111 $transactions->rollBackTo('outer');
112 $transactions->rollBackTo('inner');
113 self::assertSame([['id' => 1], ['id' => 2]], $store->get('items'));
114 $store->insert('items', [['id' => 5]]);
115 $transactions->rollBackTo('outer');
116 self::assertSame([['id' => 1], ['id' => 2]], $store->get('items'));
117 $store->insert('items', [['id' => 6]]);
118 $transactions->rollBack();
119 self::assertSame([['id' => 1]], $store->get('items'));
120 }
121
122 public function testPositionOfIsNothingForANameNothingWasDeclaredUnder(): void
123 {
124 $store = new ShadowStore();
125 $store->set('items', [['id' => 1]]);
126 $transactions = new ShadowTransactions($store);
127
128 $transactions->savepoint('outer');
129 $store->insert('items', [['id' => 2]]);
130 $transactions->savepoint('inner');
131 $store->insert('items', [['id' => 3]]);
132 $transactions->release('missing');
133 $transactions->release('inner');
134 $store->insert('items', [['id' => 4]]);
135 $transactions->rollBackTo('inner');
136 self::assertSame([['id' => 1], ['id' => 2], ['id' => 3], ['id' => 4]], $store->get('items'));
137 $transactions->rollBackTo('outer');
138
139 self::assertSame([['id' => 1]], $store->get('items'));
140 }
141
142 public function testBeginIsIgnoredWhereATransactionHasAlreadyBegun(): void
143 {
144 $store = new ShadowStore();
145 $store->set('order', [['id' => 1]]);
146 $transactions = new ShadowTransactions($store);
147
148 $transactions->begin();
149 $store->set('order', []);
150 $transactions->begin();
151 $transactions->rollBack();
152
153 self::assertSame([['id' => 1]], $store->get('order'));
154 }
155
156 public function testCommitLeavesNothingToRollBackTo(): void
157 {
158 $store = new ShadowStore();
159 $store->set('order', [['id' => 1]]);
160 $transactions = new ShadowTransactions($store);
161
162 $transactions->begin();
163 $store->set('order', []);
164 $transactions->commit();
165 $transactions->rollBack();
166
167 self::assertSame([], $store->get('order'));
168 }
169}
170