xref: /plugin/sqlite/helper.php (revision 54f742ccd52c2a9322c33c77ae38bd3f1993148f)
1<?php
2
3/**
4 * @noinspection SqlNoDataSourceInspection
5 * @noinspection SqlDialectInspection
6 * @noinspection PhpComposerExtensionStubsInspection
7 */
8
9use dokuwiki\plugin\sqlite\SQLiteDB;
10use dokuwiki\plugin\sqlite\Tools;
11
12
13
14/**
15 * For compatibility with previous adapter implementation.
16 */
17if(!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo');
18class helper_plugin_sqlite_adapter_dummy
19{
20    public function getName() {
21        return DOKU_EXT_PDO;
22    }
23
24    public function setUseNativeAlter($set) {}
25}
26
27/**
28 * DokuWiki Plugin sqlite (Helper Component)
29 *
30 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
31 * @author  Andreas Gohr <gohr@cosmocode.de>
32 * @deprecated 2023-03-15
33 */
34class helper_plugin_sqlite extends DokuWiki_Plugin
35{
36    /** @var SQLiteDB|null */
37    protected $adapter = null;
38
39    /** @var array result cache */
40    protected $data;
41
42    /**
43     * constructor
44     */
45    public function __construct()
46    {
47        if (!$this->existsPDOSqlite()) {
48            msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1);
49        }
50        $this->adapter = new helper_plugin_sqlite_adapter_dummy();
51    }
52
53    /**
54     * Get the current Adapter
55     * @return SQLiteDB|null
56     */
57    public function getAdapter()
58    {
59        return $this->adapter;
60    }
61
62    /**
63     * Keep separate instances for every call to keep database connections
64     */
65    public function isSingleton()
66    {
67        return false;
68    }
69
70    /**
71     * check availabilty of PHP PDO sqlite3
72     */
73    public function existsPDOSqlite()
74    {
75        if (class_exists('pdo')) {
76            return in_array('sqlite', \PDO::getAvailableDrivers());
77        }
78        return false;
79    }
80
81    /**
82     * Initializes and opens the database
83     *
84     * Needs to be called right after loading this helper plugin
85     *
86     * @param string $dbname
87     * @param string $updatedir - Database update infos
88     * @return bool
89     */
90    public function init($dbname, $updatedir)
91    {
92        if(!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests
93            dbg_deprecated(SQLiteDB::class);
94        }
95
96        try {
97            $this->adapter = new SQLiteDB($dbname, $updatedir, $this);
98        } catch (Exception $e) {
99            msg('SQLite: ' . $e->getMessage(), -1);
100            return false;
101        }
102        return true;
103    }
104
105    /**
106     * This is called from the adapter itself for backwards compatibility
107     *
108     * @param SQLiteDB $adapter
109     * @return void
110     */
111    function setAdapter($adapter)
112    {
113        $this->adapter = $adapter;
114    }
115
116    /**
117     * Registers a User Defined Function for use in SQL statements
118     */
119    public function create_function($function_name, $callback, $num_args)
120    {
121        $this->adapter->getDb()->sqliteCreateFunction($function_name, $callback, $num_args);
122    }
123
124    // region query and result handling functions
125
126    /**
127     * Convenience function to run an INSERT OR REPLACE operation
128     *
129     * The function takes a key-value array with the column names in the key and the actual value in the value,
130     * build the appropriate query and executes it.
131     *
132     * @param string $table the table the entry should be saved to (will not be escaped)
133     * @param array $entry A simple key-value pair array (only values will be escaped)
134     * @return bool
135     */
136    public function storeEntry($table, $entry)
137    {
138        try {
139            $this->adapter->saveRecord($table, $entry);
140        } catch (\Exception $e) {
141            msg('SQLite: ' . $e->getMessage(), -1);
142            return false;
143        }
144
145        return true;
146    }
147
148    /**
149     * Execute a query with the given parameters.
150     *
151     * Takes care of escaping
152     *
153     *
154     * @param string ...$args - the arguments of query(), the first is the sql and others are values
155     */
156    public function query()
157    {
158        // get function arguments
159        $args = func_get_args();
160        $sql = array_shift($args);
161
162        // check if args contains single array
163        if (isset($args[0]) && is_array($args[0])) {
164            $args = $args[0];
165        }
166
167        // clear the cache
168        $this->data = null;
169
170        try {
171            return $this->adapter->query($sql, $args);
172        } catch (\Exception $e) {
173            msg('SQLite: ' . $e->getMessage(), -1);
174            return false;
175        }
176    }
177
178
179    /**
180     * Closes the result set (and it's cursors)
181     *
182     * If you're doing SELECT queries inside a TRANSACTION, be sure to call this
183     * function on all your results sets, before COMMITing the transaction.
184     *
185     * Also required when not all rows of a result are fetched
186     *
187     * @param \PDOStatement $res
188     * @return bool
189     */
190    public function res_close($res)
191    {
192        if (!$res) return false;
193
194        return $res->closeCursor();
195    }
196
197    /**
198     * Returns a complete result set as array
199     *
200     * @param \PDOStatement $res
201     * @return array
202     */
203    public function res2arr($res, $assoc = true)
204    {
205        if (!$res) return [];
206
207        // this is a bullshit workaround for having res2arr and res2count work on one result
208        if (!$this->data) {
209            $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM;
210            $this->data = $res->fetchAll($mode);
211        }
212        return $this->data;
213    }
214
215    /**
216     * Return the next row from the result set as associative array
217     *
218     * @param \PDOStatement $res
219     * @param int $rownum will be ignored
220     */
221    public function res2row($res, $rownum = 0)
222    {
223        if (!$res) return false;
224
225        return $res->fetch(\PDO::FETCH_ASSOC);
226    }
227
228    /**
229     * Return the first value from the next row.
230     *
231     * @param \PDOStatement $res
232     * @return mixed
233     */
234    public function res2single($res)
235    {
236        if (!$res) return false;
237
238        $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0);
239        if (empty($data)) {
240            return false;
241        }
242        return $data[0];
243    }
244
245    /**
246     * fetch the next row as zero indexed array
247     *
248     * @param \PDOStatement $res
249     * @return array|bool
250     */
251    public function res_fetch_array($res)
252    {
253        if (!$res) return false;
254
255        return $res->fetch(PDO::FETCH_NUM);
256    }
257
258    /**
259     * fetch the next row as assocative array
260     *
261     * @param \PDOStatement $res
262     * @return array|bool
263     */
264    public function res_fetch_assoc($res)
265    {
266        if (!$res) return false;
267
268        return $res->fetch(PDO::FETCH_ASSOC);
269    }
270
271    /**
272     * Count the number of records in result
273     *
274     * This function is really inperformant in PDO and should be avoided!
275     *
276     * @param \PDOStatement $res
277     * @return int
278     */
279    public function res2count($res)
280    {
281        if (!$res) return 0;
282
283        // this is a bullshit workaround for having res2arr and res2count work on one result
284        if (!$this->data) {
285            $this->data = $this->res2arr($res);
286        }
287
288        return count($this->data);
289    }
290
291    /**
292     * Count the number of records changed last time
293     *
294     * @param \PDOStatement $res
295     * @return int
296     */
297    public function countChanges($res)
298    {
299        if (!$res) return 0;
300
301        return $res->rowCount();
302    }
303
304    // endregion
305
306    // region quoting/escaping functions
307
308    /**
309     * Join the given values and quote them for SQL insertion
310     */
311    public function quote_and_join($vals, $sep = ',')
312    {
313        $vals = array_map([$this->adapter->getDb(), 'quote'], $vals);
314        return join($sep, $vals);
315    }
316
317    /**
318     * Quotes a string, by escaping it and adding quotes
319     */
320    public function quote_string($string)
321    {
322        return $this->adapter->getDb()->quote($string);
323    }
324
325    /**
326     * Similar to quote_string, but without the quotes, useful to construct LIKE patterns
327     */
328    public function escape_string($str)
329    {
330        return trim($this->adapter->getDb()->quote($str), "'");
331    }
332
333    // endregion
334
335    // region speciality functions
336
337    /**
338     * Split sql queries on semicolons, unless when semicolons are quoted
339     *
340     * Usually you don't need this. It's only really needed if you need individual results for
341     * multiple queries. For example in the admin interface.
342     *
343     * @param string $sql
344     * @return array sql queries
345     * @deprecated
346     */
347    public function SQLstring2array($sql)
348    {
349        if(!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests
350            dbg_deprecated(Tools::class . '::SQLstring2array');
351        }
352        return Tools::SQLstring2array($sql);
353    }
354
355    /**
356     * @deprecated needs to be fixed in stuct and structpublish
357     */
358    public function doTransaction($sql, $sqlpreparing = true) {
359        throw new \Exception(
360            'This method seems to never have done what it suggests. Please use the query() function instead.'
361        );
362    }
363
364    // endregion
365}
366