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