Skip to content

Commit 8fffea3

Browse files
committed
test(sharding): fix test doubles to capture rollback queries
- Ensure rollback queries and commands are properly captured in tests
1 parent 0e19567 commit 8fffea3

File tree

4 files changed

+556
-5
lines changed

4 files changed

+556
-5
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
Copyright (c) 2024, Manticore Software LTD (https://manticoresearch.com)
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License version 3 or any later
8+
version. You should have received a copy of the GPL license along with this
9+
program; if you did not, you can find it at http://www.gnu.org/
10+
*/
11+
12+
use Manticoresearch\BuddyTest\Plugin\Sharding\TestDoubles\TestableQueue;
13+
use Manticoresearch\BuddyTest\Plugin\Sharding\TestDoubles\TestableTable;
14+
use PHPUnit\Framework\TestCase;
15+
16+
/**
17+
* Integration tests for rollback functionality with Table operations
18+
* Tests the interaction between Table operations and Queue rollback system
19+
*/
20+
final class QueueRollbackIntegrationTest extends TestCase {
21+
22+
private TestableQueue $queue;
23+
private TestableTable $table;
24+
25+
protected function setUp(): void {
26+
$this->queue = new TestableQueue();
27+
$this->table = new TestableTable();
28+
}
29+
30+
/**
31+
* Test table creation with rollback commands
32+
*/
33+
public function testTableCreationWithRollback(): void {
34+
// Since TestableTable might not have all methods, we'll test the queue directly
35+
// This simulates what would happen during table shard creation
36+
37+
// Add some typical table creation commands with rollback
38+
$this->queue->add('node1', 'CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0', 'shard_create_test');
39+
$this->queue->add('node1', 'CREATE TABLE test_s1 (id bigint)', 'DROP TABLE IF EXISTS test_s1', 'shard_create_test');
40+
$this->queue->add('node1', 'CREATE TABLE test type=\'distributed\' local=\'test_s0,test_s1\'', 'DROP TABLE IF EXISTS test', 'shard_create_test');
41+
42+
// Verify queue has commands with rollback
43+
$commands = $this->queue->getCapturedCommands();
44+
$this->assertNotEmpty($commands);
45+
$this->assertCount(3, $commands);
46+
47+
foreach ($commands as $command) {
48+
$this->assertArrayHasKey('rollback_query', $command);
49+
$this->assertNotEmpty($command['rollback_query']);
50+
$this->assertEquals('shard_create_test', $command['operation_group']);
51+
52+
// Verify rollback patterns
53+
if (str_contains($command['query'], 'CREATE TABLE') && !str_contains($command['query'], 'distributed')) {
54+
$this->assertStringContainsString('DROP TABLE IF EXISTS', $command['rollback_query']);
55+
} elseif (str_contains($command['query'], 'CREATE CLUSTER')) {
56+
$this->assertStringContainsString('DELETE CLUSTER', $command['rollback_query']);
57+
} elseif (str_contains($command['query'], 'ALTER CLUSTER') && str_contains($command['query'], 'ADD')) {
58+
$this->assertStringContainsString('ALTER CLUSTER', $command['rollback_query']);
59+
$this->assertStringContainsString('DROP', $command['rollback_query']);
60+
}
61+
}
62+
}
63+
64+
/**
65+
* Test table drop operations with empty rollback (destructive)
66+
*/
67+
public function testTableDropWithEmptyRollback(): void {
68+
// Simulate table drop commands
69+
$this->queue->add('node1', 'DROP TABLE IF EXISTS test_s0', '', 'drop_operation');
70+
$this->queue->add('node1', 'DROP TABLE IF EXISTS test_s1', '', 'drop_operation');
71+
$this->queue->add('node1', 'DROP TABLE IF EXISTS test', '', 'drop_operation');
72+
73+
// Verify drop commands have empty rollback (can't undo destructive operations)
74+
$commands = $this->queue->getCapturedCommands();
75+
$this->assertNotEmpty($commands);
76+
77+
foreach ($commands as $command) {
78+
if (!str_contains($command['query'], 'DROP TABLE')) {
79+
continue;
80+
}
81+
82+
$this->assertEquals('', $command['rollback_query']);
83+
}
84+
}
85+
86+
/**
87+
* Test operation groups for atomic rollback
88+
*/
89+
public function testOperationGroupsForAtomicRollback(): void {
90+
$operationGroup = 'shard_create_test_' . uniqid();
91+
92+
// Add multiple commands with same operation group
93+
$this->queue->add('node1', 'CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0', $operationGroup);
94+
$this->queue->add('node1', 'CREATE TABLE test_s1 (id bigint)', 'DROP TABLE IF EXISTS test_s1', $operationGroup);
95+
$this->queue->add('node1', 'CREATE TABLE test type=\'distributed\'', 'DROP TABLE IF EXISTS test', $operationGroup);
96+
97+
// Verify all commands have the same operation group
98+
$commands = $this->queue->getCapturedCommands();
99+
foreach ($commands as $command) {
100+
$this->assertEquals($operationGroup, $command['operation_group']);
101+
}
102+
}
103+
104+
/**
105+
* Test rollback execution for failed table creation
106+
*/
107+
public function testRollbackExecutionForFailedCreation(): void {
108+
$operationGroup = 'failed_creation_test';
109+
110+
// Add some commands that would be part of table creation
111+
$this->queue->add('node1', 'CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0', $operationGroup);
112+
$this->queue->add('node1', 'CREATE CLUSTER temp_cluster', 'DELETE CLUSTER temp_cluster', $operationGroup);
113+
114+
// Test rollback execution
115+
$result = $this->queue->rollbackOperationGroup($operationGroup);
116+
$this->assertTrue($result); // Should succeed in test environment
117+
}
118+
119+
/**
120+
* Test complex rebalancing scenario with rollback
121+
*/
122+
public function testRebalancingWithRollback(): void {
123+
$operationGroup = 'rebalance_test_' . uniqid();
124+
125+
// Simulate RF=1 rebalancing with intermediate clusters
126+
$rebalanceCommands = [
127+
// Create shard on new node
128+
['node2', 'CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0'],
129+
130+
// Create intermediate cluster for data movement
131+
['node1', 'CREATE CLUSTER temp_move_0_123', 'DELETE CLUSTER temp_move_0_123'],
132+
['node1', 'ALTER CLUSTER temp_move_0_123 ADD test_s0', 'ALTER CLUSTER temp_move_0_123 DROP test_s0'],
133+
['node2', 'JOIN CLUSTER temp_move_0_123', 'DELETE CLUSTER temp_move_0_123'],
134+
135+
// Complete the move
136+
['node1', 'ALTER CLUSTER temp_move_0_123 DROP test_s0', 'ALTER CLUSTER temp_move_0_123 ADD test_s0'],
137+
['node1', 'DROP TABLE test_s0', ''], // Original shard removal (destructive)
138+
['node1', 'DELETE CLUSTER temp_move_0_123', ''], // Cleanup (destructive)
139+
140+
// Update distributed table
141+
['node1', 'DROP TABLE test', ''],
142+
['node1', 'CREATE TABLE test type=\'distributed\' local=\'test_s1\' agent=\'node2:test_s0\'', 'DROP TABLE IF EXISTS test'],
143+
];
144+
145+
foreach ($rebalanceCommands as [$node, $query, $rollback]) {
146+
$this->queue->add($node, $query, $rollback, $operationGroup);
147+
}
148+
149+
// Verify rebalancing commands have proper rollback
150+
$commands = $this->queue->getCapturedCommands();
151+
$this->assertCount(count($rebalanceCommands), $commands);
152+
153+
// Check for shard movement commands with rollback
154+
$shardMovementCommands = array_filter(
155+
$commands, function ($cmd) {
156+
return str_contains($cmd['query'], 'temp_move_') ||
157+
str_contains($cmd['query'], 'CREATE CLUSTER') ||
158+
str_contains($cmd['query'], 'JOIN CLUSTER');
159+
}
160+
);
161+
162+
$this->assertNotEmpty($shardMovementCommands);
163+
164+
foreach ($shardMovementCommands as $command) {
165+
// Non-destructive operations should have rollback commands
166+
if (str_contains($command['query'], 'DROP') || str_contains($command['query'], 'DELETE')) {
167+
continue;
168+
}
169+
170+
$this->assertNotEmpty($command['rollback_query']);
171+
}
172+
}
173+
174+
/**
175+
* Test rollback command patterns for different operations
176+
*/
177+
public function testRollbackCommandPatterns(): void {
178+
$testOperations = [
179+
// Table operations
180+
['CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0'],
181+
['CREATE TABLE test_s1 (id bigint)', 'DROP TABLE IF EXISTS test_s1'],
182+
183+
// Cluster operations
184+
['CREATE CLUSTER temp_cluster', 'DELETE CLUSTER temp_cluster'],
185+
['ALTER CLUSTER temp_cluster ADD test_s0', 'ALTER CLUSTER temp_cluster DROP test_s0'],
186+
['JOIN CLUSTER temp_cluster', 'DELETE CLUSTER temp_cluster'],
187+
188+
// Destructive operations (empty rollback)
189+
['DROP TABLE test_s0', ''],
190+
['DELETE CLUSTER temp_cluster', ''],
191+
];
192+
193+
foreach ($testOperations as [$query, $expectedRollback]) {
194+
$this->queue->add('node1', $query, $expectedRollback);
195+
}
196+
197+
$commands = $this->queue->getCapturedCommands();
198+
$this->assertCount(count($testOperations), $commands);
199+
200+
for ($i = 0; $i < count($testOperations); $i++) {
201+
$this->assertEquals($testOperations[$i][0], $commands[$i]['query']);
202+
$this->assertEquals($testOperations[$i][1], $commands[$i]['rollback_query']);
203+
}
204+
}
205+
206+
/**
207+
* Test rollback with intermediate cluster cleanup
208+
*/
209+
public function testRollbackWithIntermediateClusterCleanup(): void {
210+
$operationGroup = 'move_op_1';
211+
212+
// Simulate RF=1 shard movement with intermediate clusters
213+
$moveOperations = [
214+
['node1', 'CREATE TABLE test_s0 (id bigint)', 'DROP TABLE IF EXISTS test_s0'],
215+
['node1', 'CREATE CLUSTER temp_move_0_123', 'DELETE CLUSTER temp_move_0_123'],
216+
['node1', 'ALTER CLUSTER temp_move_0_123 ADD test_s0', 'ALTER CLUSTER temp_move_0_123 DROP test_s0'],
217+
['node2', 'JOIN CLUSTER temp_move_0_123', 'DELETE CLUSTER temp_move_0_123'],
218+
['node1', 'ALTER CLUSTER temp_move_0_123 DROP test_s0', 'ALTER CLUSTER temp_move_0_123 ADD test_s0'],
219+
['node1', 'DROP TABLE test_s0', ''], // Destructive
220+
['node1', 'DELETE CLUSTER temp_move_0_123', ''], // Cleanup
221+
];
222+
223+
foreach ($moveOperations as [$node, $query, $rollback]) {
224+
$this->queue->add($node, $query, $rollback, $operationGroup);
225+
}
226+
227+
// Test rollback of the entire operation group
228+
$result = $this->queue->rollbackOperationGroup($operationGroup);
229+
$this->assertTrue($result);
230+
231+
// Verify all commands were added with correct operation group
232+
$commands = $this->queue->getCapturedCommands();
233+
foreach ($commands as $command) {
234+
$this->assertEquals($operationGroup, $command['operation_group']);
235+
}
236+
}
237+
238+
/**
239+
* Test rollback system handles different command types correctly
240+
*/
241+
public function testRollbackWithDifferentCommandTypes(): void {
242+
$operationGroup = 'mixed_commands_test';
243+
244+
// Mix of different command types
245+
$commands = [
246+
// Reversible operations
247+
['CREATE TABLE users (id bigint)', 'DROP TABLE IF EXISTS users'],
248+
['CREATE CLUSTER main_cluster', 'DELETE CLUSTER main_cluster'],
249+
['ALTER CLUSTER main_cluster ADD users', 'ALTER CLUSTER main_cluster DROP users'],
250+
251+
// Destructive operations (no rollback possible)
252+
['DROP TABLE temp_table', ''],
253+
['DELETE CLUSTER temp_cluster', ''],
254+
255+
// Complex operations
256+
['CREATE TABLE distributed_users type=\'distributed\' local=\'users\'', 'DROP TABLE IF EXISTS distributed_users'],
257+
];
258+
259+
foreach ($commands as [$query, $rollback]) {
260+
$this->queue->add('node1', $query, $rollback, $operationGroup);
261+
}
262+
263+
$capturedCommands = $this->queue->getCapturedCommands();
264+
$this->assertCount(count($commands), $capturedCommands);
265+
266+
// Verify rollback patterns
267+
foreach ($capturedCommands as $i => $command) {
268+
$expectedRollback = $commands[$i][1];
269+
$this->assertEquals($expectedRollback, $command['rollback_query']);
270+
$this->assertEquals($operationGroup, $command['operation_group']);
271+
}
272+
273+
// Test rollback execution
274+
$result = $this->queue->rollbackOperationGroup($operationGroup);
275+
$this->assertTrue($result);
276+
}
277+
}

0 commit comments

Comments
 (0)