xref: /plugin/struct/action/migration.php (revision 76a74c230f7336dce2880f047cb3dbd1a2e8f0e7)
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    }
67*76a74c23SAnna Dabrowska
68*76a74c23SAnna Dabrowska    /**
69*76a74c23SAnna Dabrowska     * Executes Migration 16
70*76a74c23SAnna Dabrowska     *
71*76a74c23SAnna Dabrowska     * Unifies previous page and lookup schema types
72*76a74c23SAnna Dabrowska     *
73*76a74c23SAnna Dabrowska     * @param helper_plugin_sqlite $sqlite
74*76a74c23SAnna Dabrowska     * @return bool
75*76a74c23SAnna Dabrowska     */
76*76a74c23SAnna Dabrowska    protected function migration16(helper_plugin_sqlite $sqlite)
77*76a74c23SAnna Dabrowska    {
78*76a74c23SAnna Dabrowska        // get tables and their SQL definitions
79*76a74c23SAnna Dabrowska        $sql = "SELECT sql, name FROM sqlite_master
80*76a74c23SAnna Dabrowska                WHERE type = 'table'
81*76a74c23SAnna Dabrowska                AND (name LIKE 'data_%' OR name LIKE 'multi_%')";
82*76a74c23SAnna Dabrowska        $res = $sqlite->query($sql);
83*76a74c23SAnna Dabrowska        $tables = $sqlite->res2arr($res);
84*76a74c23SAnna Dabrowska        $sqlite->res_close($res);
85*76a74c23SAnna Dabrowska
86*76a74c23SAnna Dabrowska        // get latest versions of schemas with islookup property
87*76a74c23SAnna Dabrowska        $sql = "SELECT MAX(id) AS id, tbl, islookup FROM schemas
88*76a74c23SAnna Dabrowska                    GROUP BY tbl
89*76a74c23SAnna Dabrowska            ";
90*76a74c23SAnna Dabrowska        $res = $sqlite->query($sql);
91*76a74c23SAnna Dabrowska        $schemas = $sqlite->res2arr($res);
92*76a74c23SAnna Dabrowska
93*76a74c23SAnna Dabrowska        $sqlite->query('BEGIN TRANSACTION');
94*76a74c23SAnna Dabrowska        $ok = true;
95*76a74c23SAnna Dabrowska
96*76a74c23SAnna Dabrowska        // Step 1: move original data to temporary tables and create new ones with modified schemas
97*76a74c23SAnna Dabrowska        foreach($tables as $table) {
98*76a74c23SAnna Dabrowska            $name = $table['name'];
99*76a74c23SAnna Dabrowska            $sql = $table['sql'];
100*76a74c23SAnna Dabrowska
101*76a74c23SAnna Dabrowska            // move original data to temp_*
102*76a74c23SAnna Dabrowska            $ok = $ok && $sqlite->query("ALTER TABLE $name RENAME TO temp_$name");
103*76a74c23SAnna Dabrowska
104*76a74c23SAnna Dabrowska            // update pid definitions
105*76a74c23SAnna Dabrowska            $sql = preg_replace('/pid (\w* ?NOT NULL|\w* ?PRIMARY KEY)/', 'pid TEXT DEFAULT ""', $sql);
106*76a74c23SAnna Dabrowska
107*76a74c23SAnna Dabrowska            // add rid and new primary key to regular tables
108*76a74c23SAnna Dabrowska            $cnt = 0;
109*76a74c23SAnna Dabrowska            $sql = preg_replace('/(PRIMARY KEY ?\([^\)]+?)(\))/', ' rid INTEGER, $1, rid $2', $sql, -1, $cnt);
110*76a74c23SAnna Dabrowska            // add rid and new primary key to lookup tables
111*76a74c23SAnna Dabrowska            if (!$cnt) {
112*76a74c23SAnna Dabrowska                $sql = str_replace(')', ', rid INTEGER, PRIMARY KEY(pid,rid) )', $sql);
113*76a74c23SAnna Dabrowska            }
114*76a74c23SAnna Dabrowska
115*76a74c23SAnna Dabrowska            // create the new table
116*76a74c23SAnna Dabrowska            $ok = $ok && $sqlite->query($sql);
117*76a74c23SAnna Dabrowska            if (!$ok) return false;
118*76a74c23SAnna Dabrowska        }
119*76a74c23SAnna Dabrowska
120*76a74c23SAnna Dabrowska        // Step 2: transfer data back from original tables (temp_*)
121*76a74c23SAnna Dabrowska        foreach($schemas as $schema) {
122*76a74c23SAnna Dabrowska            $name = $schema['tbl'];
123*76a74c23SAnna Dabrowska            $sid = $schema['id'];
124*76a74c23SAnna Dabrowska            $isLookup = $schema['islookup'];
125*76a74c23SAnna Dabrowska
126*76a74c23SAnna Dabrowska            if (!$isLookup) {
127*76a74c23SAnna Dabrowska                $s = sprintf('INSERT INTO data_%s SELECT *, 0 FROM temp_data_%s', $name, $name);
128*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
129*76a74c23SAnna Dabrowska                if (!$ok) return false;
130*76a74c23SAnna Dabrowska
131*76a74c23SAnna Dabrowska                $s = sprintf('INSERT INTO multi_%s SELECT *, 0 FROM temp_multi_%s', $name, $name);
132*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
133*76a74c23SAnna Dabrowska                if (!$ok) return false;
134*76a74c23SAnna Dabrowska            } else {
135*76a74c23SAnna Dabrowska                // transfer pid to rid
136*76a74c23SAnna Dabrowska                $s = sprintf('INSERT INTO data_%s SELECT *, pid FROM temp_data_%s', $name, $name);
137*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
138*76a74c23SAnna Dabrowska                if (!$ok) return false;
139*76a74c23SAnna Dabrowska
140*76a74c23SAnna Dabrowska                $s = sprintf('INSERT INTO multi_%s SELECT *, pid FROM temp_multi_%s', $name, $name);
141*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
142*76a74c23SAnna Dabrowska                if (!$ok) return false;
143*76a74c23SAnna Dabrowska
144*76a74c23SAnna Dabrowska                // all lookup data has empty pids at this point
145*76a74c23SAnna Dabrowska                $s = "UPDATE data_$name SET pid = ''";
146*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
147*76a74c23SAnna Dabrowska                if (!$ok) return false;
148*76a74c23SAnna Dabrowska
149*76a74c23SAnna Dabrowska                $s = "UPDATE multi_$name SET pid = ''";
150*76a74c23SAnna Dabrowska                $ok = $ok && $sqlite->query($s);
151*76a74c23SAnna Dabrowska                if (!$ok) return false;
152*76a74c23SAnna Dabrowska            }
153*76a74c23SAnna Dabrowska
154*76a74c23SAnna Dabrowska            // introduce composite ids in lookup columns
155*76a74c23SAnna Dabrowska            $s = "SELECT C.colref AS COL, T.class AS TYPE
156*76a74c23SAnna Dabrowska                FROM schema_cols AS C
157*76a74c23SAnna Dabrowska                LEFT OUTER JOIN types AS T
158*76a74c23SAnna Dabrowska                    ON C.tid = T.id
159*76a74c23SAnna Dabrowska                WHERE C.sid = $sid
160*76a74c23SAnna Dabrowska                AND TYPE = 'Lookup'
161*76a74c23SAnna Dabrowska            ";
162*76a74c23SAnna Dabrowska            $res = $sqlite->query($s);
163*76a74c23SAnna Dabrowska            $cols = $sqlite->res2arr($res);
164*76a74c23SAnna Dabrowska
165*76a74c23SAnna Dabrowska            if ($cols) {
166*76a74c23SAnna Dabrowska                foreach ($cols as $col) {
167*76a74c23SAnna Dabrowska                    $colno = $col['COL'];
168*76a74c23SAnna Dabrowska                    $s = "UPDATE data_$name SET col$colno = '[". '""' . ",'||col$colno||']' WHERE col$colno != ''";
169*76a74c23SAnna Dabrowska                    $ok = $ok && $sqlite->query($s);
170*76a74c23SAnna Dabrowska                    if (!$ok) return false;
171*76a74c23SAnna Dabrowska
172*76a74c23SAnna Dabrowska                    // multi_
173*76a74c23SAnna Dabrowska                    $s = "UPDATE multi_$name SET value = '[". '""' . ",'||value||']' WHERE colref=$colno";
174*76a74c23SAnna Dabrowska                    $ok = $ok && $sqlite->query($s);
175*76a74c23SAnna Dabrowska                    if (!$ok) return false;
176*76a74c23SAnna Dabrowska                }
177*76a74c23SAnna Dabrowska            }
178*76a74c23SAnna Dabrowska        }
179*76a74c23SAnna Dabrowska
180*76a74c23SAnna Dabrowska        // Step 3: delete temp_* tables
181*76a74c23SAnna Dabrowska        foreach($tables as $table) {
182*76a74c23SAnna Dabrowska            $name = $table['name'];
183*76a74c23SAnna Dabrowska            $s = "DROP TABLE temp_$name";
184*76a74c23SAnna Dabrowska            $ok = $ok && $sqlite->query($s);
185*76a74c23SAnna Dabrowska            if (!$ok) return false;
186*76a74c23SAnna Dabrowska        }
187*76a74c23SAnna Dabrowska
188*76a74c23SAnna Dabrowska        // Step 4: remove islookup in schemas table
189*76a74c23SAnna Dabrowska        $sql = "SELECT sql FROM sqlite_master
190*76a74c23SAnna Dabrowska                WHERE type = 'table'
191*76a74c23SAnna Dabrowska                AND name = 'schemas'";
192*76a74c23SAnna Dabrowska        $res = $sqlite->query($sql);
193*76a74c23SAnna Dabrowska        $t = $sqlite->res2arr($res);
194*76a74c23SAnna Dabrowska        $sql = $t[0]['sql'];
195*76a74c23SAnna Dabrowska        $sql = str_replace('islookup INTEGER,', '', $sql);
196*76a74c23SAnna Dabrowska
197*76a74c23SAnna Dabrowska        $s = 'ALTER TABLE schemas RENAME TO temp_schemas';
198*76a74c23SAnna Dabrowska        $ok = $ok && $sqlite->query($s);
199*76a74c23SAnna Dabrowska        if (!$ok) return false;
200*76a74c23SAnna Dabrowska
201*76a74c23SAnna Dabrowska        // create a new table without islookup
202*76a74c23SAnna Dabrowska        $ok = $ok && $sqlite->query($sql);
203*76a74c23SAnna Dabrowska        if (!$ok) return false;
204*76a74c23SAnna Dabrowska
205*76a74c23SAnna Dabrowska        $s = 'INSERT INTO schemas SELECT id, tbl, ts, user, comment, config FROM temp_schemas';
206*76a74c23SAnna Dabrowska        $ok = $ok && $sqlite->query($s);
207*76a74c23SAnna Dabrowska
208*76a74c23SAnna Dabrowska        if (!$ok) {
209*76a74c23SAnna Dabrowska            $sqlite->query('ROLLBACK TRANSACTION');
210*76a74c23SAnna Dabrowska            return false;
211*76a74c23SAnna Dabrowska        }
212*76a74c23SAnna Dabrowska        $sqlite->query('COMMIT TRANSACTION');
213*76a74c23SAnna Dabrowska        return true;
214*76a74c23SAnna Dabrowska    }
2158f259467SAndreas Gohr}
216