xref: /plugin/sqlite/SQLiteDB.php (revision 8da7d8059f7b9860b1f87a4aab15e87bf96c4064)
1*8da7d805SAndreas Gohr<?php
2*8da7d805SAndreas Gohr
3*8da7d805SAndreas Gohr/**
4*8da7d805SAndreas Gohr * @noinspection SqlNoDataSourceInspection
5*8da7d805SAndreas Gohr * @noinspection SqlDialectInspection
6*8da7d805SAndreas Gohr * @noinspection PhpComposerExtensionStubsInspection
7*8da7d805SAndreas Gohr */
8*8da7d805SAndreas Gohr
9*8da7d805SAndreas Gohrnamespace dokuwiki\plugin\sqlite;
10*8da7d805SAndreas Gohr
11*8da7d805SAndreas Gohr
12*8da7d805SAndreas Gohr/**
13*8da7d805SAndreas Gohr * Helpers to access a SQLite Database with automatic schema migration
14*8da7d805SAndreas Gohr */
15*8da7d805SAndreas Gohrclass SQLiteDB
16*8da7d805SAndreas Gohr{
17*8da7d805SAndreas Gohr    const FILE_EXTENSION = '.sqlite3';
18*8da7d805SAndreas Gohr
19*8da7d805SAndreas Gohr    /** @var \PDO */
20*8da7d805SAndreas Gohr    protected $pdo;
21*8da7d805SAndreas Gohr
22*8da7d805SAndreas Gohr    /** @var string */
23*8da7d805SAndreas Gohr    protected $schemadir;
24*8da7d805SAndreas Gohr
25*8da7d805SAndreas Gohr    /** @var string */
26*8da7d805SAndreas Gohr    protected $dbname;
27*8da7d805SAndreas Gohr
28*8da7d805SAndreas Gohr    /** @var \helper_plugin_sqlite
29*8da7d805SAndreas Gohr    protected $helper;
30*8da7d805SAndreas Gohr
31*8da7d805SAndreas Gohr    /**
32*8da7d805SAndreas Gohr     * Constructor
33*8da7d805SAndreas Gohr     *
34*8da7d805SAndreas Gohr     * @param string $dbname Database name
35*8da7d805SAndreas Gohr     * @param string $schemadir directory with schema migration files
36*8da7d805SAndreas Gohr     * @param \helper_plugin_sqlite $sqlitehelper for backwards compatibility
37*8da7d805SAndreas Gohr     * @throws \Exception
38*8da7d805SAndreas Gohr     */
39*8da7d805SAndreas Gohr    public function __construct($dbname, $schemadir, $sqlitehelper = null)
40*8da7d805SAndreas Gohr    {
41*8da7d805SAndreas Gohr        if (!class_exists('pdo') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
42*8da7d805SAndreas Gohr            throw new \Exception('SQLite PDO driver not available');
43*8da7d805SAndreas Gohr        }
44*8da7d805SAndreas Gohr
45*8da7d805SAndreas Gohr        // backwards compatibility, circular dependency
46*8da7d805SAndreas Gohr        $this->helper = $sqlitehelper;
47*8da7d805SAndreas Gohr        if($this->helper) $this->helper->setAdapter($this);
48*8da7d805SAndreas Gohr
49*8da7d805SAndreas Gohr        $this->schemadir = $schemadir;
50*8da7d805SAndreas Gohr        $this->dbname = $dbname;
51*8da7d805SAndreas Gohr        $file = $this->getDbFile();
52*8da7d805SAndreas Gohr
53*8da7d805SAndreas Gohr        $this->pdo = new \PDO(
54*8da7d805SAndreas Gohr            'sqlite:' . $file,
55*8da7d805SAndreas Gohr            null,
56*8da7d805SAndreas Gohr            null,
57*8da7d805SAndreas Gohr            [
58*8da7d805SAndreas Gohr                \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
59*8da7d805SAndreas Gohr            ]
60*8da7d805SAndreas Gohr        );
61*8da7d805SAndreas Gohr
62*8da7d805SAndreas Gohr        if($schemadir !== '') {
63*8da7d805SAndreas Gohr            // schema dir is empty, when accessing the DB from Admin interface instead of plugin context
64*8da7d805SAndreas Gohr            $this->applyMigrations();
65*8da7d805SAndreas Gohr        }
66*8da7d805SAndreas Gohr        Functions::register($this->pdo);
67*8da7d805SAndreas Gohr    }
68*8da7d805SAndreas Gohr
69*8da7d805SAndreas Gohr
70*8da7d805SAndreas Gohr    // region public API
71*8da7d805SAndreas Gohr
72*8da7d805SAndreas Gohr    /**
73*8da7d805SAndreas Gohr     * Direct access to the PDO object
74*8da7d805SAndreas Gohr     * @return \PDO
75*8da7d805SAndreas Gohr     */
76*8da7d805SAndreas Gohr    public function pdo()
77*8da7d805SAndreas Gohr    {
78*8da7d805SAndreas Gohr        return $this->pdo;
79*8da7d805SAndreas Gohr    }
80*8da7d805SAndreas Gohr
81*8da7d805SAndreas Gohr    /**
82*8da7d805SAndreas Gohr     * Execute a statement and return it
83*8da7d805SAndreas Gohr     *
84*8da7d805SAndreas Gohr     * @param string $sql
85*8da7d805SAndreas Gohr     * @param array $parameters
86*8da7d805SAndreas Gohr     * @return \PDOStatement Be sure to close the cursor yourself
87*8da7d805SAndreas Gohr     * @throws \PDOException
88*8da7d805SAndreas Gohr     */
89*8da7d805SAndreas Gohr    public function query($sql, $parameters = [])
90*8da7d805SAndreas Gohr    {
91*8da7d805SAndreas Gohr        $stmt = $this->pdo->prepare($sql);
92*8da7d805SAndreas Gohr        $stmt->execute($parameters);
93*8da7d805SAndreas Gohr        return $stmt;
94*8da7d805SAndreas Gohr    }
95*8da7d805SAndreas Gohr
96*8da7d805SAndreas Gohr    /**
97*8da7d805SAndreas Gohr     * Execute a statement and return metadata
98*8da7d805SAndreas Gohr     *
99*8da7d805SAndreas Gohr     * Returns the last insert ID on INSERTs or the number of affected rows
100*8da7d805SAndreas Gohr     *
101*8da7d805SAndreas Gohr     * @param string $sql
102*8da7d805SAndreas Gohr     * @param array $parameters
103*8da7d805SAndreas Gohr     * @return int
104*8da7d805SAndreas Gohr     * @throws \PDOException
105*8da7d805SAndreas Gohr     */
106*8da7d805SAndreas Gohr    public function exec($sql, $parameters = [])
107*8da7d805SAndreas Gohr    {
108*8da7d805SAndreas Gohr        $stmt = $this->pdo->prepare($sql);
109*8da7d805SAndreas Gohr        $stmt->execute($parameters);
110*8da7d805SAndreas Gohr
111*8da7d805SAndreas Gohr        $count = $stmt->rowCount();
112*8da7d805SAndreas Gohr        $stmt->closeCursor();
113*8da7d805SAndreas Gohr        if ($count && preg_match('/^INSERT /i', $sql)) {
114*8da7d805SAndreas Gohr            return $this->queryValue('SELECT last_insert_rowid()');
115*8da7d805SAndreas Gohr        }
116*8da7d805SAndreas Gohr
117*8da7d805SAndreas Gohr        return $count;
118*8da7d805SAndreas Gohr    }
119*8da7d805SAndreas Gohr
120*8da7d805SAndreas Gohr    /**
121*8da7d805SAndreas Gohr     * Simple query abstraction
122*8da7d805SAndreas Gohr     *
123*8da7d805SAndreas Gohr     * Returns all data
124*8da7d805SAndreas Gohr     *
125*8da7d805SAndreas Gohr     * @param string $sql
126*8da7d805SAndreas Gohr     * @param array $params
127*8da7d805SAndreas Gohr     * @return array
128*8da7d805SAndreas Gohr     * @throws \PDOException
129*8da7d805SAndreas Gohr     */
130*8da7d805SAndreas Gohr    public function queryAll($sql, $params = [])
131*8da7d805SAndreas Gohr    {
132*8da7d805SAndreas Gohr        $stmt = $this->query($sql, $params);
133*8da7d805SAndreas Gohr        $data = $stmt->fetchAll(\PDO::FETCH_ASSOC);
134*8da7d805SAndreas Gohr        $stmt->closeCursor();
135*8da7d805SAndreas Gohr        return $data;
136*8da7d805SAndreas Gohr    }
137*8da7d805SAndreas Gohr
138*8da7d805SAndreas Gohr    /**
139*8da7d805SAndreas Gohr     * Query one single row
140*8da7d805SAndreas Gohr     *
141*8da7d805SAndreas Gohr     * @param string $sql
142*8da7d805SAndreas Gohr     * @param array $params
143*8da7d805SAndreas Gohr     * @return array|null
144*8da7d805SAndreas Gohr     * @throws \PDOException
145*8da7d805SAndreas Gohr     */
146*8da7d805SAndreas Gohr    public function queryRecord($sql, $params = [])
147*8da7d805SAndreas Gohr    {
148*8da7d805SAndreas Gohr        $stmt = $this->query($sql, $params);
149*8da7d805SAndreas Gohr        $row = $stmt->fetch();
150*8da7d805SAndreas Gohr        $stmt->closeCursor();
151*8da7d805SAndreas Gohr        if (is_array($row) && count($row)) return $row;
152*8da7d805SAndreas Gohr        return null;
153*8da7d805SAndreas Gohr    }
154*8da7d805SAndreas Gohr
155*8da7d805SAndreas Gohr    /**
156*8da7d805SAndreas Gohr     * Insert or replace the given data into the table
157*8da7d805SAndreas Gohr     *
158*8da7d805SAndreas Gohr     * @param string $table
159*8da7d805SAndreas Gohr     * @param array $data
160*8da7d805SAndreas Gohr     * @param bool $replace Conflict resolution, replace or ignore
161*8da7d805SAndreas Gohr     * @throws \PDOException
162*8da7d805SAndreas Gohr     */
163*8da7d805SAndreas Gohr    public function saveRecord($table, $data, $replace = true)
164*8da7d805SAndreas Gohr    {
165*8da7d805SAndreas Gohr        $columns = array_map(function ($column) {
166*8da7d805SAndreas Gohr            return '"' . $column . '"';
167*8da7d805SAndreas Gohr        }, array_keys($data));
168*8da7d805SAndreas Gohr        $values = array_values($data);
169*8da7d805SAndreas Gohr        $placeholders = array_pad([], count($columns), '?');
170*8da7d805SAndreas Gohr
171*8da7d805SAndreas Gohr        if ($replace) {
172*8da7d805SAndreas Gohr            $command = 'REPLACE';
173*8da7d805SAndreas Gohr        } else {
174*8da7d805SAndreas Gohr            $command = 'INSERT OR IGNORE';
175*8da7d805SAndreas Gohr        }
176*8da7d805SAndreas Gohr
177*8da7d805SAndreas Gohr        /** @noinspection SqlResolve */
178*8da7d805SAndreas Gohr        $sql = $command . ' INTO "' . $table . '" (' . join(',', $columns) . ') VALUES (' . join(',', $placeholders) . ')';
179*8da7d805SAndreas Gohr        $stm = $this->pdo->prepare($sql);
180*8da7d805SAndreas Gohr        $stm->execute($values);
181*8da7d805SAndreas Gohr        $stm->closeCursor();
182*8da7d805SAndreas Gohr    }
183*8da7d805SAndreas Gohr
184*8da7d805SAndreas Gohr    /**
185*8da7d805SAndreas Gohr     * Execute a query that returns a single value
186*8da7d805SAndreas Gohr     *
187*8da7d805SAndreas Gohr     * @param string $sql
188*8da7d805SAndreas Gohr     * @param array $params
189*8da7d805SAndreas Gohr     * @return mixed|null
190*8da7d805SAndreas Gohr     * @throws \PDOException
191*8da7d805SAndreas Gohr     */
192*8da7d805SAndreas Gohr    public function queryValue($sql, $params = [])
193*8da7d805SAndreas Gohr    {
194*8da7d805SAndreas Gohr        $result = $this->queryAll($sql, $params);
195*8da7d805SAndreas Gohr        if (is_array($result) && count($result)) return array_values($result[0])[0];
196*8da7d805SAndreas Gohr        return null;
197*8da7d805SAndreas Gohr    }
198*8da7d805SAndreas Gohr
199*8da7d805SAndreas Gohr    // endregion
200*8da7d805SAndreas Gohr
201*8da7d805SAndreas Gohr    // region meta handling
202*8da7d805SAndreas Gohr
203*8da7d805SAndreas Gohr    /**
204*8da7d805SAndreas Gohr     * Get a config value from the opt table
205*8da7d805SAndreas Gohr     *
206*8da7d805SAndreas Gohr     * @param string $opt Config name
207*8da7d805SAndreas Gohr     * @param mixed $default What to return if the value isn't set
208*8da7d805SAndreas Gohr     * @return mixed
209*8da7d805SAndreas Gohr     * @throws \PDOException
210*8da7d805SAndreas Gohr     */
211*8da7d805SAndreas Gohr    public function getOpt($opt, $default = null)
212*8da7d805SAndreas Gohr    {
213*8da7d805SAndreas Gohr        $value = $this->queryValue("SELECT val FROM opts WHERE opt = ?", [$opt]);
214*8da7d805SAndreas Gohr        if ($value === null) return $default;
215*8da7d805SAndreas Gohr        return $value;
216*8da7d805SAndreas Gohr    }
217*8da7d805SAndreas Gohr
218*8da7d805SAndreas Gohr    /**
219*8da7d805SAndreas Gohr     * Set a config value in the opt table
220*8da7d805SAndreas Gohr     *
221*8da7d805SAndreas Gohr     * @param $opt
222*8da7d805SAndreas Gohr     * @param $value
223*8da7d805SAndreas Gohr     * @throws \PDOException
224*8da7d805SAndreas Gohr     */
225*8da7d805SAndreas Gohr    public function setOpt($opt, $value)
226*8da7d805SAndreas Gohr    {
227*8da7d805SAndreas Gohr        $this->exec('REPLACE INTO opts (opt,val) VALUES (?,?)', [$opt, $value]);
228*8da7d805SAndreas Gohr    }
229*8da7d805SAndreas Gohr
230*8da7d805SAndreas Gohr    /**
231*8da7d805SAndreas Gohr     * @return string
232*8da7d805SAndreas Gohr     */
233*8da7d805SAndreas Gohr    public function getDbName()
234*8da7d805SAndreas Gohr    {
235*8da7d805SAndreas Gohr        return $this->dbname;
236*8da7d805SAndreas Gohr    }
237*8da7d805SAndreas Gohr
238*8da7d805SAndreas Gohr    /**
239*8da7d805SAndreas Gohr     * @return string
240*8da7d805SAndreas Gohr     */
241*8da7d805SAndreas Gohr    public function getDbFile()
242*8da7d805SAndreas Gohr    {
243*8da7d805SAndreas Gohr        global $conf;
244*8da7d805SAndreas Gohr        return $conf['metadir'] . '/' . $this->dbname . self::FILE_EXTENSION;
245*8da7d805SAndreas Gohr    }
246*8da7d805SAndreas Gohr
247*8da7d805SAndreas Gohr    /**
248*8da7d805SAndreas Gohr     * Create a dump of the database and its contents
249*8da7d805SAndreas Gohr     *
250*8da7d805SAndreas Gohr     * @return string
251*8da7d805SAndreas Gohr     * @throws \Exception
252*8da7d805SAndreas Gohr     */
253*8da7d805SAndreas Gohr    public function dumpToFile($filename)
254*8da7d805SAndreas Gohr    {
255*8da7d805SAndreas Gohr        $fp = fopen($filename, 'w');
256*8da7d805SAndreas Gohr        if (!$fp) {
257*8da7d805SAndreas Gohr            throw new \Exception('Could not open file ' . $filename . ' for writing');
258*8da7d805SAndreas Gohr        }
259*8da7d805SAndreas Gohr
260*8da7d805SAndreas Gohr
261*8da7d805SAndreas Gohr        $tables = $this->queryAll('SELECT name,sql FROM sqlite_master WHERE type="table"');
262*8da7d805SAndreas Gohr        fwrite($fp, 'BEGIN TRANSACTION;' . "\n");
263*8da7d805SAndreas Gohr
264*8da7d805SAndreas Gohr        foreach ($tables as $table) {
265*8da7d805SAndreas Gohr            fwrite($fp, $table['sql'] . ";\n"); // table definition
266*8da7d805SAndreas Gohr
267*8da7d805SAndreas Gohr            // data as INSERT statements
268*8da7d805SAndreas Gohr            $sql = "SELECT * FROM " . $table['name'];
269*8da7d805SAndreas Gohr            $res = $this->query($sql);
270*8da7d805SAndreas Gohr            while ($row = $res->fetch(\PDO::FETCH_ASSOC)) {
271*8da7d805SAndreas Gohr                $line = 'INSERT INTO ' . $table['name'] . ' VALUES(';
272*8da7d805SAndreas Gohr                foreach ($row as $no_entry => $entry) {
273*8da7d805SAndreas Gohr                    if ($no_entry !== 0) {
274*8da7d805SAndreas Gohr                        $line .= ',';
275*8da7d805SAndreas Gohr                    }
276*8da7d805SAndreas Gohr
277*8da7d805SAndreas Gohr                    if (is_null($entry)) {
278*8da7d805SAndreas Gohr                        $line .= 'NULL';
279*8da7d805SAndreas Gohr                    } elseif (!is_numeric($entry)) {
280*8da7d805SAndreas Gohr                        $line .= $this->pdo->quote($entry);
281*8da7d805SAndreas Gohr                    } else {
282*8da7d805SAndreas Gohr                        // TODO depending on locale extra leading zeros
283*8da7d805SAndreas Gohr                        // are truncated e.g 1.300 (thousand three hunderd)-> 1.3
284*8da7d805SAndreas Gohr                        $line .= $entry;
285*8da7d805SAndreas Gohr                    }
286*8da7d805SAndreas Gohr                }
287*8da7d805SAndreas Gohr                $line .= ');' . "\n";
288*8da7d805SAndreas Gohr                fwrite($fp, $line);
289*8da7d805SAndreas Gohr            }
290*8da7d805SAndreas Gohr            $res->closeCursor();
291*8da7d805SAndreas Gohr        }
292*8da7d805SAndreas Gohr
293*8da7d805SAndreas Gohr        // indexes
294*8da7d805SAndreas Gohr        $indexes = $this->queryAll("SELECT name,sql FROM sqlite_master WHERE type='index'");
295*8da7d805SAndreas Gohr        foreach ($indexes as $index) {
296*8da7d805SAndreas Gohr            fwrite($fp, $index['sql'] . ";\n");
297*8da7d805SAndreas Gohr        }
298*8da7d805SAndreas Gohr        fwrite($fp, 'COMMIT;' . "\n");
299*8da7d805SAndreas Gohr        fclose($fp);
300*8da7d805SAndreas Gohr        return $filename;
301*8da7d805SAndreas Gohr    }
302*8da7d805SAndreas Gohr
303*8da7d805SAndreas Gohr    // endregion
304*8da7d805SAndreas Gohr
305*8da7d805SAndreas Gohr    // region migration handling
306*8da7d805SAndreas Gohr
307*8da7d805SAndreas Gohr    /**
308*8da7d805SAndreas Gohr     * Apply all pending migrations
309*8da7d805SAndreas Gohr     *
310*8da7d805SAndreas Gohr     * Each migration is executed in a transaction which is rolled back on failure
311*8da7d805SAndreas Gohr     * Migrations can be files in the schema directory or event handlers
312*8da7d805SAndreas Gohr     *
313*8da7d805SAndreas Gohr     * @throws \Exception
314*8da7d805SAndreas Gohr     */
315*8da7d805SAndreas Gohr    protected function applyMigrations()
316*8da7d805SAndreas Gohr    {
317*8da7d805SAndreas Gohr        $currentVersion = $this->currentDbVersion();
318*8da7d805SAndreas Gohr        $latestVersion = $this->latestDbVersion();
319*8da7d805SAndreas Gohr
320*8da7d805SAndreas Gohr        for ($newVersion = $currentVersion + 1; $newVersion <= $latestVersion; $newVersion++) {
321*8da7d805SAndreas Gohr            $data = [
322*8da7d805SAndreas Gohr                'dbname' => $this->dbname,
323*8da7d805SAndreas Gohr                'from' => $currentVersion,
324*8da7d805SAndreas Gohr                'to' => $newVersion,
325*8da7d805SAndreas Gohr                'file' => $this->getMigrationFile($newVersion),
326*8da7d805SAndreas Gohr                'sqlite' => $this->helper,
327*8da7d805SAndreas Gohr                'adapter' => $this,
328*8da7d805SAndreas Gohr            ];
329*8da7d805SAndreas Gohr            $event = new \Doku_Event('PLUGIN_SQLITE_DATABASE_UPGRADE', $data);
330*8da7d805SAndreas Gohr
331*8da7d805SAndreas Gohr            $this->pdo->beginTransaction();
332*8da7d805SAndreas Gohr            try {
333*8da7d805SAndreas Gohr                if ($event->advise_before()) {
334*8da7d805SAndreas Gohr                    // standard migration file
335*8da7d805SAndreas Gohr                    $sql = file_get_contents($data['file']);
336*8da7d805SAndreas Gohr                    $this->exec($sql);
337*8da7d805SAndreas Gohr                } else if (!$event->result) {
338*8da7d805SAndreas Gohr                    // advise before returned false, but the result was false
339*8da7d805SAndreas Gohr                    throw new \PDOException('Plugin event did not signal success');
340*8da7d805SAndreas Gohr                }
341*8da7d805SAndreas Gohr                $this->setOpt('dbversion', $newVersion);
342*8da7d805SAndreas Gohr                $this->pdo->commit();
343*8da7d805SAndreas Gohr                $event->advise_after();
344*8da7d805SAndreas Gohr            } catch (\Exception $e) {
345*8da7d805SAndreas Gohr                // something went wrong, rollback
346*8da7d805SAndreas Gohr                $this->pdo->rollBack();
347*8da7d805SAndreas Gohr                throw $e;
348*8da7d805SAndreas Gohr            }
349*8da7d805SAndreas Gohr        }
350*8da7d805SAndreas Gohr
351*8da7d805SAndreas Gohr        // vacuum the database to free up unused space
352*8da7d805SAndreas Gohr        $this->pdo->exec('VACUUM');
353*8da7d805SAndreas Gohr    }
354*8da7d805SAndreas Gohr
355*8da7d805SAndreas Gohr    /**
356*8da7d805SAndreas Gohr     * Read the current version from the opt table
357*8da7d805SAndreas Gohr     *
358*8da7d805SAndreas Gohr     * The opt table is created here if not found
359*8da7d805SAndreas Gohr     *
360*8da7d805SAndreas Gohr     * @return int
361*8da7d805SAndreas Gohr     * @throws \PDOException
362*8da7d805SAndreas Gohr     */
363*8da7d805SAndreas Gohr    protected function currentDbVersion()
364*8da7d805SAndreas Gohr    {
365*8da7d805SAndreas Gohr        try {
366*8da7d805SAndreas Gohr            $version = $this->getOpt('dbversion', 0);
367*8da7d805SAndreas Gohr            return (int)$version;
368*8da7d805SAndreas Gohr        } catch (\PDOException $ignored) {
369*8da7d805SAndreas Gohr            // add the opt table - if this fails too, let the exception bubble up
370*8da7d805SAndreas Gohr            $sql = "CREATE TABLE IF NOT EXISTS opts (opt TEXT NOT NULL PRIMARY KEY, val NOT NULL DEFAULT '')";
371*8da7d805SAndreas Gohr            $this->exec($sql);
372*8da7d805SAndreas Gohr            $this->setOpt('dbversion', 0);
373*8da7d805SAndreas Gohr            return 0;
374*8da7d805SAndreas Gohr        }
375*8da7d805SAndreas Gohr    }
376*8da7d805SAndreas Gohr
377*8da7d805SAndreas Gohr    /**
378*8da7d805SAndreas Gohr     * Get the version this db should have
379*8da7d805SAndreas Gohr     *
380*8da7d805SAndreas Gohr     * @return int
381*8da7d805SAndreas Gohr     * @throws \PDOException
382*8da7d805SAndreas Gohr     */
383*8da7d805SAndreas Gohr    protected function latestDbVersion()
384*8da7d805SAndreas Gohr    {
385*8da7d805SAndreas Gohr        if (!file_exists($this->schemadir . '/latest.version')) {
386*8da7d805SAndreas Gohr            throw new \PDOException('No latest.version in schema dir');
387*8da7d805SAndreas Gohr        }
388*8da7d805SAndreas Gohr        return (int)trim(file_get_contents($this->schemadir . '/latest.version'));
389*8da7d805SAndreas Gohr    }
390*8da7d805SAndreas Gohr
391*8da7d805SAndreas Gohr    /**
392*8da7d805SAndreas Gohr     * Get the migrartion file for the given version
393*8da7d805SAndreas Gohr     *
394*8da7d805SAndreas Gohr     * @param int $version
395*8da7d805SAndreas Gohr     * @return string
396*8da7d805SAndreas Gohr     */
397*8da7d805SAndreas Gohr    protected function getMigrationFile($version)
398*8da7d805SAndreas Gohr    {
399*8da7d805SAndreas Gohr        return sprintf($this->schemadir . '/update%04d.sql', $version);
400*8da7d805SAndreas Gohr    }
401*8da7d805SAndreas Gohr    // endregion
402*8da7d805SAndreas Gohr}
403