18f259467SAndreas Gohr<?php 2d6d97f60SAnna Dabrowska 38f259467SAndreas Gohr/** 48f259467SAndreas Gohr * DokuWiki Plugin struct (Action Component) 58f259467SAndreas Gohr * 68f259467SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 78f259467SAndreas Gohr * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 88f259467SAndreas Gohr */ 98f259467SAndreas Gohr 108f259467SAndreas Gohr/** 118f259467SAndreas Gohr * Class action_plugin_struct_migration 128f259467SAndreas Gohr * 138f259467SAndreas Gohr * Handle migrations that need more than just SQL 148f259467SAndreas Gohr */ 15d6d97f60SAnna Dabrowskaclass action_plugin_struct_migration extends DokuWiki_Action_Plugin 16d6d97f60SAnna Dabrowska{ 178f259467SAndreas Gohr /** 188f259467SAndreas Gohr * @inheritDoc 198f259467SAndreas Gohr */ 20d6d97f60SAnna Dabrowska public function register(Doku_Event_Handler $controller) 21d6d97f60SAnna Dabrowska { 22748e747fSAnna Dabrowska $controller->register_hook('PLUGIN_SQLITE_DATABASE_UPGRADE', 'BEFORE', $this, 'handleMigrations'); 238f259467SAndreas Gohr } 248f259467SAndreas Gohr 258f259467SAndreas Gohr /** 268f259467SAndreas Gohr * Call our custom migrations when defined 278f259467SAndreas Gohr * 288f259467SAndreas Gohr * @param Doku_Event $event 298f259467SAndreas Gohr * @param $param 308f259467SAndreas Gohr */ 31748e747fSAnna Dabrowska public function handleMigrations(Doku_Event $event, $param) 32d6d97f60SAnna Dabrowska { 333eafcbabSMichael Große if ($event->data['sqlite']->getAdapter()->getDbname() !== 'struct') { 343eafcbabSMichael Große return; 353eafcbabSMichael Große } 368f259467SAndreas Gohr $to = $event->data['to']; 378f259467SAndreas Gohr 388f259467SAndreas Gohr if (is_callable(array($this, "migration$to"))) { 398f259467SAndreas Gohr $event->preventDefault(); 408f259467SAndreas Gohr $event->result = call_user_func(array($this, "migration$to"), $event->data['sqlite']); 418f259467SAndreas Gohr } 428f259467SAndreas Gohr } 438f259467SAndreas Gohr 448f259467SAndreas Gohr /** 458f259467SAndreas Gohr * Executes Migration 12 468f259467SAndreas Gohr * 478f259467SAndreas Gohr * Add a latest column to all existing multi tables 488f259467SAndreas Gohr * 498f259467SAndreas Gohr * @param helper_plugin_sqlite $sqlite 508f259467SAndreas Gohr * @return bool 518f259467SAndreas Gohr */ 52d6d97f60SAnna Dabrowska protected function migration12(helper_plugin_sqlite $sqlite) 53d6d97f60SAnna Dabrowska { 548f259467SAndreas Gohr /** @noinspection SqlResolve */ 558f259467SAndreas Gohr $sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'multi_%'"; 568f259467SAndreas Gohr $res = $sqlite->query($sql); 578f259467SAndreas Gohr $tables = $sqlite->res2arr($res); 588f259467SAndreas Gohr $sqlite->res_close($res); 598f259467SAndreas Gohr 608f259467SAndreas Gohr foreach ($tables as $row) { 618f259467SAndreas Gohr $sql = 'ALTER TABLE ? ADD COLUMN latest INT DEFAULT 1'; 628f259467SAndreas Gohr $sqlite->query($sql, $row['name']); 638f259467SAndreas Gohr } 648f259467SAndreas Gohr 658f259467SAndreas Gohr return true; 668f259467SAndreas Gohr } 6776a74c23SAnna Dabrowska 6876a74c23SAnna Dabrowska /** 6976a74c23SAnna Dabrowska * Executes Migration 16 7076a74c23SAnna Dabrowska * 7176a74c23SAnna Dabrowska * Unifies previous page and lookup schema types 7276a74c23SAnna Dabrowska * 7376a74c23SAnna Dabrowska * @param helper_plugin_sqlite $sqlite 7476a74c23SAnna Dabrowska * @return bool 7576a74c23SAnna Dabrowska */ 7676a74c23SAnna Dabrowska protected function migration16(helper_plugin_sqlite $sqlite) 7776a74c23SAnna Dabrowska { 7876a74c23SAnna Dabrowska // get tables and their SQL definitions 7976a74c23SAnna Dabrowska $sql = "SELECT sql, name FROM sqlite_master 8076a74c23SAnna Dabrowska WHERE type = 'table' 8176a74c23SAnna Dabrowska AND (name LIKE 'data_%' OR name LIKE 'multi_%')"; 8276a74c23SAnna Dabrowska $res = $sqlite->query($sql); 8376a74c23SAnna Dabrowska $tables = $sqlite->res2arr($res); 8476a74c23SAnna Dabrowska $sqlite->res_close($res); 8576a74c23SAnna Dabrowska 8676a74c23SAnna Dabrowska // get latest versions of schemas with islookup property 8776a74c23SAnna Dabrowska $sql = "SELECT MAX(id) AS id, tbl, islookup FROM schemas 8876a74c23SAnna Dabrowska GROUP BY tbl 8976a74c23SAnna Dabrowska "; 9076a74c23SAnna Dabrowska $res = $sqlite->query($sql); 9176a74c23SAnna Dabrowska $schemas = $sqlite->res2arr($res); 9276a74c23SAnna Dabrowska 9376a74c23SAnna Dabrowska $sqlite->query('BEGIN TRANSACTION'); 9476a74c23SAnna Dabrowska $ok = true; 9576a74c23SAnna Dabrowska 9676a74c23SAnna Dabrowska // Step 1: move original data to temporary tables and create new ones with modified schemas 9776a74c23SAnna Dabrowska foreach ($tables as $table) { 9876a74c23SAnna Dabrowska $name = $table['name']; 9976a74c23SAnna Dabrowska $sql = $table['sql']; 10076a74c23SAnna Dabrowska 10176a74c23SAnna Dabrowska // move original data to temp_* 10276a74c23SAnna Dabrowska $ok = $ok && $sqlite->query("ALTER TABLE $name RENAME TO temp_$name"); 10376a74c23SAnna Dabrowska 10476a74c23SAnna Dabrowska // update pid definitions 10576a74c23SAnna Dabrowska $sql = preg_replace('/pid (\w* ?NOT NULL|\w* ?PRIMARY KEY)/', 'pid TEXT DEFAULT ""', $sql); 10676a74c23SAnna Dabrowska 10776a74c23SAnna Dabrowska // add rid and new primary key to regular tables 10876a74c23SAnna Dabrowska $cnt = 0; 10976a74c23SAnna Dabrowska $sql = preg_replace('/(PRIMARY KEY ?\([^\)]+?)(\))/', ' rid INTEGER, $1, rid $2', $sql, -1, $cnt); 11076a74c23SAnna Dabrowska // add rid and new primary key to lookup tables 11176a74c23SAnna Dabrowska if (!$cnt) { 11276a74c23SAnna Dabrowska $sql = str_replace(')', ', rid INTEGER, PRIMARY KEY(pid,rid) )', $sql); 11376a74c23SAnna Dabrowska } 11476a74c23SAnna Dabrowska 11576a74c23SAnna Dabrowska // create the new table 11676a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($sql); 11776a74c23SAnna Dabrowska if (!$ok) return false; 11876a74c23SAnna Dabrowska } 11976a74c23SAnna Dabrowska 12076a74c23SAnna Dabrowska // Step 2: transfer data back from original tables (temp_*) 12176a74c23SAnna Dabrowska foreach ($schemas as $schema) { 12276a74c23SAnna Dabrowska $name = $schema['tbl']; 12376a74c23SAnna Dabrowska $sid = $schema['id']; 12476a74c23SAnna Dabrowska $isLookup = $schema['islookup']; 12576a74c23SAnna Dabrowska 12676a74c23SAnna Dabrowska if (!$isLookup) { 12776a74c23SAnna Dabrowska $s = sprintf('INSERT INTO data_%s SELECT *, 0 FROM temp_data_%s', $name, $name); 12876a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 12976a74c23SAnna Dabrowska if (!$ok) return false; 13076a74c23SAnna Dabrowska 13176a74c23SAnna Dabrowska $s = sprintf('INSERT INTO multi_%s SELECT *, 0 FROM temp_multi_%s', $name, $name); 13276a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 13376a74c23SAnna Dabrowska if (!$ok) return false; 13476a74c23SAnna Dabrowska } else { 13576a74c23SAnna Dabrowska // transfer pid to rid 13676a74c23SAnna Dabrowska $s = sprintf('INSERT INTO data_%s SELECT *, pid FROM temp_data_%s', $name, $name); 13776a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 13876a74c23SAnna Dabrowska if (!$ok) return false; 13976a74c23SAnna Dabrowska 14076a74c23SAnna Dabrowska $s = sprintf('INSERT INTO multi_%s SELECT *, pid FROM temp_multi_%s', $name, $name); 14176a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 14276a74c23SAnna Dabrowska if (!$ok) return false; 14376a74c23SAnna Dabrowska 14476a74c23SAnna Dabrowska // all lookup data has empty pids at this point 14576a74c23SAnna Dabrowska $s = "UPDATE data_$name SET pid = ''"; 14676a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 14776a74c23SAnna Dabrowska if (!$ok) return false; 14876a74c23SAnna Dabrowska 14976a74c23SAnna Dabrowska $s = "UPDATE multi_$name SET pid = ''"; 15076a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 15176a74c23SAnna Dabrowska if (!$ok) return false; 15276a74c23SAnna Dabrowska } 15376a74c23SAnna Dabrowska 15476a74c23SAnna Dabrowska // introduce composite ids in lookup columns 15576a74c23SAnna Dabrowska $s = "SELECT C.colref AS COL, T.class AS TYPE 15676a74c23SAnna Dabrowska FROM schema_cols AS C 15776a74c23SAnna Dabrowska LEFT OUTER JOIN types AS T 15876a74c23SAnna Dabrowska ON C.tid = T.id 15976a74c23SAnna Dabrowska WHERE C.sid = $sid 1608acbe1a4SSzymon Olewniczak AND TYPE LIKE '%Lookup' 16176a74c23SAnna Dabrowska "; 16276a74c23SAnna Dabrowska $res = $sqlite->query($s); 16376a74c23SAnna Dabrowska $cols = $sqlite->res2arr($res); 16476a74c23SAnna Dabrowska 16576a74c23SAnna Dabrowska if ($cols) { 16676a74c23SAnna Dabrowska foreach ($cols as $col) { 16776a74c23SAnna Dabrowska $colno = $col['COL']; 168*e361da24SAnna Dabrowska // simple lookup fields 169*e361da24SAnna Dabrowska $s = "UPDATE data_$name SET col$colno = '[" . '""' . ",'||col$colno||']' WHERE col$colno != '' AND CAST(col$colno AS DECIMAL) = col$colno"; 170*e361da24SAnna Dabrowska $ok = $ok && $sqlite->query($s); 171*e361da24SAnna Dabrowska if (!$ok) return false; 172*e361da24SAnna Dabrowska // multi_ 173*e361da24SAnna Dabrowska $s = "UPDATE multi_$name SET value = '[" . '""' . ",'||value||']' WHERE colref=$colno AND CAST(value AS DECIMAL) = value"; 17476a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 17576a74c23SAnna Dabrowska if (!$ok) return false; 17676a74c23SAnna Dabrowska 177*e361da24SAnna Dabrowska // lookup fields pointing to pages 178*e361da24SAnna Dabrowska $colname = "col$colno"; 179*e361da24SAnna Dabrowska $f = 'UPDATE data_%s SET %s = \'["\'||%s||\'",0]\' WHERE %s != \'\' AND CAST(%s AS DECIMAL) != %s'; 180*e361da24SAnna Dabrowska $s = sprintf($f, $name, $colname, $colname, $colname, $colname, $colname); 181*e361da24SAnna Dabrowska $ok = $ok && $sqlite->query($s); 182*e361da24SAnna Dabrowska if (!$ok) return false; 18376a74c23SAnna Dabrowska // multi_ 184*e361da24SAnna Dabrowska $f = 'UPDATE multi_%s SET value = \'["value",0]\' WHERE colref = %s AND CAST(value AS DECIMAL) != value'; 185*e361da24SAnna Dabrowska $s = sprintf($f, $name, $colno); 18676a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 18776a74c23SAnna Dabrowska if (!$ok) return false; 18876a74c23SAnna Dabrowska } 18976a74c23SAnna Dabrowska } 19076a74c23SAnna Dabrowska } 19176a74c23SAnna Dabrowska 19276a74c23SAnna Dabrowska // Step 3: delete temp_* tables 19376a74c23SAnna Dabrowska foreach ($tables as $table) { 19476a74c23SAnna Dabrowska $name = $table['name']; 19576a74c23SAnna Dabrowska $s = "DROP TABLE temp_$name"; 19676a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 19776a74c23SAnna Dabrowska if (!$ok) return false; 19876a74c23SAnna Dabrowska } 19976a74c23SAnna Dabrowska 20076a74c23SAnna Dabrowska // Step 4: remove islookup in schemas table 20176a74c23SAnna Dabrowska $sql = "SELECT sql FROM sqlite_master 20276a74c23SAnna Dabrowska WHERE type = 'table' 20376a74c23SAnna Dabrowska AND name = 'schemas'"; 20476a74c23SAnna Dabrowska $res = $sqlite->query($sql); 20576a74c23SAnna Dabrowska $t = $sqlite->res2arr($res); 20676a74c23SAnna Dabrowska $sql = $t[0]['sql']; 20776a74c23SAnna Dabrowska $sql = str_replace('islookup INTEGER,', '', $sql); 20876a74c23SAnna Dabrowska 20976a74c23SAnna Dabrowska $s = 'ALTER TABLE schemas RENAME TO temp_schemas'; 21076a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 21176a74c23SAnna Dabrowska if (!$ok) return false; 21276a74c23SAnna Dabrowska 21376a74c23SAnna Dabrowska // create a new table without islookup 21476a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($sql); 21576a74c23SAnna Dabrowska if (!$ok) return false; 21676a74c23SAnna Dabrowska 21776a74c23SAnna Dabrowska $s = 'INSERT INTO schemas SELECT id, tbl, ts, user, comment, config FROM temp_schemas'; 21876a74c23SAnna Dabrowska $ok = $ok && $sqlite->query($s); 21976a74c23SAnna Dabrowska 22076a74c23SAnna Dabrowska if (!$ok) { 22176a74c23SAnna Dabrowska $sqlite->query('ROLLBACK TRANSACTION'); 22276a74c23SAnna Dabrowska return false; 22376a74c23SAnna Dabrowska } 22476a74c23SAnna Dabrowska $sqlite->query('COMMIT TRANSACTION'); 22576a74c23SAnna Dabrowska return true; 22676a74c23SAnna Dabrowska } 2278f259467SAndreas Gohr} 228