dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxDatabaseMigrationService_test.php
Go to the documentation of this file.
1<?php
2
3declare(strict_types=1);
4
5$root = dirname(__DIR__, 4);
6chdir($root);
7$_SERVER['REQUEST_URI'] = '/dbxapp/';
8$_SERVER['HTTP_HOST'] = 'localhost';
9$_SERVER['HTTPS'] = 'on';
10$_SERVER['SCRIPT_NAME'] = '/dbxapp/index.php';
11
12if (!defined('dbxSystem')) {
13 define('dbxSystem', 'dbxWebApp');
14}
15if (!defined('dbxRunAsAdmin')) {
16 define('dbxRunAsAdmin', 1);
17}
18
19require_once $root . '/dbx/vendor/autoload.php';
20require_once $root . '/dbx/include/dbxKernel.php';
21require_once dirname(__DIR__) . '/include/dbxDatabaseMigrationService.class.php';
22
24
25final class MigrationDbStub
26{
27 public array $rows = array();
28 public array $writes = array();
29 public array $bindingOverride = array();
30 private int $nextId = 1;
31
32 public function get_dd_server(string $dd): string
33 {
35 ? 'dbxAdmin.db3'
36 : 'dbxTest.db3';
37 }
38
39 public function get_dd_table(string $dd): string
40 {
41 return match ($dd) {
43 'dbx|dbxUser' => 'dbx_user',
44 'dbx|dbxUser_groups' => 'dbx_user_groups',
45 default => '',
46 };
47 }
48
49 public function get_dd_server_binding_info(string $dd): array
50 {
51 return array_replace(array(
52 'source' => 'local-binding',
53 'valid' => true,
54 'resolved_server' => $dd === 'dbx|dbxUser'
55 ? 'mysqlUsers'
56 : 'dbxUser.db3',
57 'declared_server' => 'dbxUser.db3',
58 ), $this->bindingOverride);
59 }
60
61 public function select1(string $dd, array $where, mixed $columns, int $access): array
62 {
63 foreach ($this->rows[$dd] ?? array() as $row) {
64 $matches = true;
65 foreach ($where as $name => $value) {
66 if (($row[$name] ?? null) !== $value) {
67 $matches = false;
68 }
69 }
70 if ($matches) {
71 return $row;
72 }
73 }
74 return array();
75 }
76
77 public function insert(
78 string $dd,
79 array $values,
80 int $access,
81 int $fields,
82 int $valueCheck,
83 int $trace
84 ): int {
85 $values['id'] = $this->nextId++;
86 $this->rows[$dd][] = $values;
87 $this->writes[] = array('mode' => 'insert', 'dd' => $dd, 'values' => $values);
88 return (int)$values['id'];
89 }
90
91 public function update(
92 string $dd,
93 array $values,
94 int $id,
95 int $access,
96 int $fields,
97 int $valueCheck,
98 int $trace
99 ): int {
100 if (!isset($this->rows[$dd])) {
101 return 0;
102 }
103 foreach ($this->rows[$dd] as &$row) {
104 if ((int)($row['id'] ?? 0) === $id) {
105 $row = array_replace($row, $values);
106 break;
107 }
108 }
109 unset($row);
110 $this->writes[] = array('mode' => 'update', 'dd' => $dd, 'values' => $values);
111 return 1;
112 }
113
114 public function begin(string $dd): int
115 {
116 return 1;
117 }
118
119 public function commit(string $dd): int
120 {
121 return 1;
122 }
123
124 public function rollback(string $dd): int
125 {
126 return 1;
127 }
128}
129
131{
132 public bool $ledgerCreated = false;
133 public array $dropped = array();
134 public array $syncDefinitions = array();
135
136 public function get_table_exist(string $server, string $table): bool
137 {
138 return $table === 'dbx_migration' && $this->ledgerCreated;
139 }
140
141 public function create_db_tab(string $dd): int
142 {
143 $this->ledgerCreated = true;
144 return 1;
145 }
146
147 public function sync_dd_to_db(string $module, string $dd, string $mode): array
148 {
149 if ($mode !== 'reset') {
150 $fields = $_SESSION['dbx']['cache']['dd'][$module][$dd]['fields'] ?? array();
151 $this->syncDefinitions[$module . '|' . $dd] = array_values(array_map(
152 static fn(array $field): string => (string)($field['name'] ?? ''),
153 is_array($fields) ? $fields : array()
154 ));
155 }
156 return array(
157 'status' => $mode === 'reset' ? 'reset' : 'finished',
158 'message' => 'ok',
159 );
160 }
161
162 public function drop_db_tab(string $server, string $table): int
163 {
164 $this->dropped[] = $server . '|' . $table;
165 return 1;
166 }
167
168 public function get_db_fields(string $server, string $table): array
169 {
170 return array();
171 }
172
173 public function get_db_indexes(string $server, string $table): array
174 {
175 return array();
176 }
177}
178
179function migration_assert(bool $condition, string $message): void
180{
181 if (!$condition) {
182 throw new RuntimeException($message);
183 }
184}
185
188$service = new dbxDatabaseMigrationService($db, $dd);
189$backup = sys_get_temp_dir() . '/dbx-migration-test-' . bin2hex(random_bytes(4));
190
191try {
192 $discovered = $service->discover($root, '4.0.3');
194 isset($discovered['core-4.0.3-user-identity']),
195 'Core-Migration wurde nicht entdeckt.'
196 );
197
198 $state = $service->prepare($root, '4.0.3', $backup);
200 $state['pending'] === array('core-4.0.3-user-identity'),
201 'Ausstehende Migration wurde nicht geplant.'
202 );
203 $servers = array_column($state['backups'], 'server', 'dd');
205 ($servers['dbx|dbxUser'] ?? '') === 'mysqlUsers'
206 && ($servers['dbx|dbxUser_groups'] ?? '') === 'dbxUser.db3',
207 'Gemischte DD-Serverbindungen wurden im Migrationsplan nicht erhalten.'
208 );
209
211 $equivalentDb->bindingOverride = array(
212 'source' => 'dd-default',
213 'resolved_server' => 'dbx|dbxUser.db3',
214 'declared_server' => 'dbx|dbxUser.db3',
215 );
216 $equivalentService = new dbxDatabaseMigrationService(
218 new MigrationDdStub()
219 );
221 $root,
222 '4.0.4',
223 $backup . '/equivalent'
224 );
226 $equivalentState['pending'] === array('core-4.0.3-user-identity'),
227 'Qualifizierter und relativer SQLite-Server wurden nicht als identisch erkannt.'
228 );
229 $canonicalServer = new ReflectionMethod(
230 dbxDatabaseMigrationService::class,
231 'canonicalServerReference'
232 );
234 $canonicalServer->invoke($equivalentService, 'dbxUser.db3', 'dbx')
235 === 'dbx|dbxUser.db3',
236 'Modulrelativer SQLite-Server wurde nicht kanonisch qualifiziert.'
237 );
238
240 $changedDb->bindingOverride = array(
241 'source' => 'dd-default',
242 'resolved_server' => 'legacyUsers',
243 'declared_server' => 'legacyUsers',
244 );
245 $changedService = new dbxDatabaseMigrationService(
247 new MigrationDdStub()
248 );
250 try {
251 $changedService->prepare($root, '4.0.4', $backup . '/changed');
252 } catch (RuntimeException $exception) {
253 $serverChangeRejected = str_contains(
254 $exception->getMessage(),
255 'DD-Serverwechsel'
256 );
257 }
260 'Ein echter DD-Serverwechsel wurde nicht blockiert.'
261 );
262
263 $hadUserCache = isset($_SESSION['dbx']['cache']['dd']['dbx'])
264 && array_key_exists('dbxUser', $_SESSION['dbx']['cache']['dd']['dbx']);
266 ? $_SESSION['dbx']['cache']['dd']['dbx']['dbxUser']
267 : null;
268 $_SESSION['dbx']['cache']['dd']['dbx']['dbxUser'] = array(
269 'table' => array('server' => 'legacyUsers', 'table' => 'legacy_user'),
270 'fields' => array(array('name' => 'legacy_only')),
271 'indexes' => array(),
272 );
273
276 $applied['applied'] === array('core-4.0.3-user-identity'),
277 'Migration wurde nicht ausgefuehrt.'
278 );
280 in_array('uname', $dd->syncDefinitions['dbx|dbxUser'] ?? array(), true)
281 && !in_array('legacy_only', $dd->syncDefinitions['dbx|dbxUser'] ?? array(), true),
282 'sync_dd hat nicht die DD-Definition aus dem Releasebaum verwendet.'
283 );
285 in_array(
286 'legacy_only',
287 array_column(
288 $_SESSION['dbx']['cache']['dd']['dbx']['dbxUser']['fields'] ?? array(),
289 'name'
290 ),
291 true
292 ),
293 'Der vorherige DD-Cache wurde nach der Migration nicht wiederhergestellt.'
294 );
295 if ($hadUserCache) {
296 $_SESSION['dbx']['cache']['dd']['dbx']['dbxUser'] = $previousUserCache;
297 } else {
298 unset($_SESSION['dbx']['cache']['dd']['dbx']['dbxUser']);
299 }
300 $ledger = $db->select1(
301 dbxDatabaseMigrationService::LEDGER_DD,
302 array('migration_id' => 'core-4.0.3-user-identity'),
303 '*',
304 0
305 );
307 ($ledger['status'] ?? '') === 'finished',
308 'Migration wurde nicht als abgeschlossen protokolliert.'
309 );
310
311 $groups = $db->rows['dbx|dbxUser_groups'] ?? array();
313 array_column($groups, 'name') === array('guest', 'member', 'admin'),
314 'Core-Seeds wurden durch die Migration nicht angelegt.'
315 );
316
317 $service->rollback($state);
319 in_array('mysqlUsers|dbx_user', $dd->dropped, true)
320 && in_array('dbxUser.db3|dbx_user_groups', $dd->dropped, true),
321 'Rollback behandelt die gemischten Server nicht einzeln.'
322 );
323
324 echo "OK migration discovery, checksum ledger, mixed bindings and rollback\n";
325} finally {
326 if (is_dir($backup)) {
327 $iterator = new RecursiveIteratorIterator(
328 new RecursiveDirectoryIterator($backup, FilesystemIterator::SKIP_DOTS),
329 RecursiveIteratorIterator::CHILD_FIRST
330 );
331 foreach ($iterator as $item) {
332 $item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
333 }
334 rmdir($backup);
335 }
336}
$table['server']
Definition .dd.php:6
insert(string $dd, array $values, int $access, int $fields, int $valueCheck, int $trace)
update(string $dd, array $values, int $id, int $access, int $fields, int $valueCheck, int $trace)
select1(string $dd, array $where, mixed $columns, int $access)
drop_db_tab(string $server, string $table)
get_table_exist(string $server, string $table)
get_db_fields(string $server, string $table)
sync_dd_to_db(string $module, string $dd, string $mode)
get_db_indexes(string $server, string $table)
Engine-unabhaengiger, DD-basierter Datenbank-Migrationsrunner.
$_SERVER['REQUEST_METHOD']
$equivalentDb bindingOverride
migration_assert(bool $condition, string $message)
$_SESSION['dbx']['cache']['dd']['dbx']['dbxUser']
if(!defined( 'dbxSystem')) if(!defined( 'dbxRunAsAdmin'))
$existingDb rows['dbx|dbxUser'][]