Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Experimental pdo database class.
19
 *
20
 * @package    core_dml
21
 * @copyright  2008 Andrei Bautu
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
require_once(__DIR__.'/pdo_moodle_database.php');
28
 
29
/**
30
 * Experimental pdo database class
31
 *
32
 * @package    core_dml
33
 * @copyright  2008 Andrei Bautu
34
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class sqlite3_pdo_moodle_database extends pdo_moodle_database {
37
    protected $database_file_extension = '.sq3.php';
38
    /**
39
     * Detects if all needed PHP stuff installed.
40
     * Note: can be used before connect()
41
     * @return mixed true if ok, string if something
42
     */
43
    public function driver_installed() {
44
        if (!extension_loaded('pdo_sqlite') || !extension_loaded('pdo')){
45
            return get_string('sqliteextensionisnotpresentinphp', 'install');
46
        }
47
        return true;
48
    }
49
 
50
    /**
51
     * Returns database family type - describes SQL dialect
52
     * Note: can be used before connect()
53
     * @return string db family name (mysql, postgres, mssql, oracle, etc.)
54
     */
55
    public function get_dbfamily() {
56
        return 'sqlite';
57
    }
58
 
59
    /**
60
     * Returns more specific database driver type
61
     * Note: can be used before connect()
62
     * @return string db type mysqli, pgsql, oci, mssql, sqlsrv
63
     */
64
    protected function get_dbtype() {
65
        return 'sqlite3';
66
    }
67
 
68
    protected function configure_dbconnection() {
69
        // try to protect database file against web access;
70
        // this is required in case that the moodledata folder is web accessible and
71
        // .htaccess is not in place; requires that the database file extension is php
72
        $this->pdb->exec('CREATE TABLE IF NOT EXISTS "<?php die?>" (id int)');
73
        $this->pdb->exec('PRAGMA synchronous=OFF');
74
        $this->pdb->exec('PRAGMA short_column_names=1');
75
        $this->pdb->exec('PRAGMA encoding="UTF-8"');
76
        $this->pdb->exec('PRAGMA case_sensitive_like=0');
77
        $this->pdb->exec('PRAGMA locking_mode=NORMAL');
78
    }
79
 
80
    /**
81
     * Attempt to create the database
82
     * @param string $dbhost
83
     * @param string $dbuser
84
     * @param string $dbpass
85
     * @param string $dbname
86
     *
87
     * @return bool success
88
     */
89
    public function create_database($dbhost, $dbuser, $dbpass, $dbname, array $dboptions=null) {
90
        global $CFG;
91
 
92
        $this->dbhost = $dbhost;
93
        $this->dbuser = $dbuser;
94
        $this->dbpass = $dbpass;
95
        $this->dbname = $dbname;
96
        $filepath = $this->get_dbfilepath();
97
        $dirpath = dirname($filepath);
98
        @mkdir($dirpath, $CFG->directorypermissions, true);
99
        return touch($filepath);
100
    }
101
 
102
    /**
103
     * Returns the driver-dependent DSN for PDO based on members stored by connect.
104
     * Must be called after connect (or after $dbname, $dbhost, etc. members have been set).
105
     * @return string driver-dependent DSN
106
     */
107
    protected function get_dsn() {
108
        return 'sqlite:'.$this->get_dbfilepath();
109
    }
110
 
111
    /**
112
     * Returns the file path for the database file, computed from dbname and/or dboptions.
113
     * If dboptions['file'] is set, then it is used (use :memory: for in memory database);
114
     * else if dboptions['path'] is set, then the file will be <dboptions path>/<dbname>.sq3.php;
115
     * else if dbhost is set and not localhost, then the file will be <dbhost>/<dbname>.sq3.php;
116
     * else the file will be <moodle data path>/<dbname>.sq3.php
117
     * @return string file path to the SQLite database;
118
     */
119
    public function get_dbfilepath() {
120
        global $CFG;
121
        if (!empty($this->dboptions['file'])) {
122
            return $this->dboptions['file'];
123
        }
124
        if ($this->dbhost && $this->dbhost != 'localhost') {
125
            $path = $this->dbhost;
126
        } else {
127
            $path = $CFG->dataroot;
128
        }
129
        $path = rtrim($path, '\\/').'/';
130
        if (!empty($this->dbuser)) {
131
            $path .= $this->dbuser.'_';
132
        }
133
        $path .= $this->dbname.'_'.md5($this->dbpass).$this->database_file_extension;
134
        return $path;
135
    }
136
 
137
    /**
138
     * Return tables in database WITHOUT current prefix.
139
     * @param bool $usecache if true, returns list of cached tables.
140
     * @return array of table names in lowercase and without prefix
141
     */
142
    public function get_tables($usecache=true) {
143
        $tables = array();
144
 
145
        $sql = 'SELECT name FROM sqlite_master WHERE type="table" UNION ALL SELECT name FROM sqlite_temp_master WHERE type="table" ORDER BY name';
146
        if ($this->debug) {
147
            $this->debug_query($sql);
148
        }
149
        $rstables = $this->pdb->query($sql);
150
        foreach ($rstables as $table) {
151
            $table = $table['name'];
152
            $table = strtolower($table);
153
            if ($this->prefix !== false && $this->prefix !== '') {
154
                if (strpos($table, $this->prefix) !== 0) {
155
                    continue;
156
                }
157
                $table = substr($table, strlen($this->prefix));
158
            }
159
            $tables[$table] = $table;
160
        }
161
        return $tables;
162
    }
163
 
164
    /**
165
     * Return table indexes - everything lowercased
166
     * @param string $table The table we want to get indexes from.
167
     * @return array of arrays
168
     */
169
    public function get_indexes($table) {
170
        $indexes = array();
171
        $sql = 'PRAGMA index_list('.$this->prefix.$table.')';
172
        if ($this->debug) {
173
            $this->debug_query($sql);
174
        }
175
        $rsindexes = $this->pdb->query($sql);
176
        foreach($rsindexes as $index) {
177
            $unique = (boolean)$index['unique'];
178
            $index = $index['name'];
179
            $sql = 'PRAGMA index_info("'.$index.'")';
180
            if ($this->debug) {
181
                $this->debug_query($sql);
182
            }
183
            $rscolumns = $this->pdb->query($sql);
184
            $columns = array();
185
            foreach($rscolumns as $row) {
186
                $columns[] = strtolower($row['name']);
187
            }
188
            $index = strtolower($index);
189
            $indexes[$index]['unique'] = $unique;
190
            $indexes[$index]['columns'] = $columns;
191
        }
192
        return $indexes;
193
    }
194
 
195
    /**
196
     * Returns detailed information about columns in table.
197
     *
198
     * @param string $table name
199
     * @return array array of database_column_info objects indexed with column names
200
     */
201
    protected function fetch_columns(string $table): array {
202
        $structure = array();
203
 
204
        // get table's CREATE TABLE command (we'll need it for autoincrement fields)
205
        $sql = 'SELECT sql FROM sqlite_master WHERE type="table" AND tbl_name="'.$this->prefix.$table.'"';
206
        if ($this->debug) {
207
            $this->debug_query($sql);
208
        }
209
        $createsql = $this->pdb->query($sql)->fetch();
210
        if (!$createsql) {
211
            return false;
212
        }
213
        $createsql = $createsql['sql'];
214
 
215
        $sql = 'PRAGMA table_info("'. $this->prefix.$table.'")';
216
        if ($this->debug) {
217
            $this->debug_query($sql);
218
        }
219
        $rscolumns = $this->pdb->query($sql);
220
        foreach ($rscolumns as $row) {
221
            $columninfo = array(
222
                'name' => strtolower($row['name']), // colum names must be lowercase
223
                'not_null' =>(boolean)$row['notnull'],
224
                'primary_key' => (boolean)$row['pk'],
225
                'has_default' => !is_null($row['dflt_value']),
226
                'default_value' => $row['dflt_value'],
227
                'auto_increment' => false,
228
                'binary' => false,
229
                //'unsigned' => false,
230
            );
231
            $type = explode('(', $row['type']);
232
            $columninfo['type'] = strtolower($type[0]);
233
            if (count($type) > 1) {
234
                $size = explode(',', trim($type[1], ')'));
235
                $columninfo['max_length'] = $size[0];
236
                if (count($size) > 1) {
237
                    $columninfo['scale'] = $size[1];
238
                }
239
            }
240
            // SQLite does not have a fixed set of datatypes (ie. it accepts any string as
241
            // datatype in the CREATE TABLE command. We try to guess which type is used here
242
            switch(substr($columninfo['type'], 0, 3)) {
243
                case 'int': // int integer
244
                    if ($columninfo['primary_key'] && preg_match('/'.$columninfo['name'].'\W+integer\W+primary\W+key\W+autoincrement/im', $createsql)) {
245
                        $columninfo['meta_type'] = 'R';
246
                        $columninfo['auto_increment'] = true;
247
                    } else {
248
                        $columninfo['meta_type'] = 'I';
249
                    }
250
                    break;
251
                case 'num': // number numeric
252
                case 'rea': // real
253
                case 'dou': // double
254
                case 'flo': // float
255
                    $columninfo['meta_type'] = 'N';
256
                    break;
257
                case 'var': // varchar
258
                case 'cha': // char
259
                    $columninfo['meta_type'] = 'C';
260
                    break;
261
                case 'enu': // enums
262
                    $columninfo['meta_type'] = 'C';
263
                    break;
264
                case 'tex': // text
265
                case 'clo': // clob
266
                    $columninfo['meta_type'] = 'X';
267
                    break;
268
                case 'blo': // blob
269
                case 'non': // none
270
                    $columninfo['meta_type'] = 'B';
271
                    $columninfo['binary'] = true;
272
                    break;
273
                case 'boo': // boolean
274
                case 'bit': // bit
275
                case 'log': // logical
276
                    $columninfo['meta_type'] = 'L';
277
                    $columninfo['max_length'] = 1;
278
                    break;
279
                case 'tim': // timestamp
280
                    $columninfo['meta_type'] = 'T';
281
                    break;
282
                case 'dat': // date datetime
283
                    $columninfo['meta_type'] = 'D';
284
                    break;
285
            }
286
            if ($columninfo['has_default'] && ($columninfo['meta_type'] == 'X' || $columninfo['meta_type']== 'C')) {
287
                // trim extra quotes from text default values
288
                $columninfo['default_value'] = substr($columninfo['default_value'], 1, -1);
289
            }
290
            $structure[$columninfo['name']] = new database_column_info($columninfo);
291
        }
292
 
293
        return $structure;
294
    }
295
 
296
    /**
297
     * Normalise values based in RDBMS dependencies (booleans, LOBs...)
298
     *
299
     * @param database_column_info $column column metadata corresponding with the value we are going to normalise
300
     * @param mixed $value value we are going to normalise
301
     * @return mixed the normalised value
302
     */
303
    protected function normalise_value($column, $value) {
304
        return $value;
305
    }
306
 
307
    /**
308
     * Returns the sql statement with clauses to append used to limit a recordset range.
309
     * @param string $sql the SQL statement to limit.
310
     * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
311
     * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
312
     * @return string the SQL statement with limiting clauses
313
     */
314
    protected function get_limit_clauses($sql, $limitfrom=0, $limitnum=0) {
315
        if ($limitnum) {
316
            $sql .= ' LIMIT '.$limitnum;
317
            if ($limitfrom) {
318
                $sql .= ' OFFSET '.$limitfrom;
319
            }
320
        }
321
        return $sql;
322
    }
323
 
324
    /**
325
     * Delete the records from a table where all the given conditions met.
326
     * If conditions not specified, table is truncated.
327
     *
328
     * @param string $table the table to delete from.
329
     * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
330
     * @return returns success.
331
     */
332
    public function delete_records($table, array $conditions=null) {
333
        if (is_null($conditions)) {
334
            return $this->execute("DELETE FROM {{$table}}");
335
        }
336
        list($select, $params) = $this->where_clause($table, $conditions);
337
        return $this->delete_records_select($table, $select, $params);
338
    }
339
 
340
    /**
341
     * Returns the proper SQL to do CONCAT between the elements passed
342
     * Can take many parameters
343
     *
344
     * @param string $elements,...
345
     * @return string
346
     */
347
    public function sql_concat(...$elements) {
348
        return implode('||', $elements);
349
    }
350
 
351
    /**
352
     * Returns the proper SQL to do CONCAT between the elements passed
353
     * with a given separator
354
     *
355
     * @param string $separator
356
     * @param array  $elements
357
     * @return string
358
     */
359
    public function sql_concat_join($separator="' '", $elements=array()) {
360
        // Intersperse $elements in the array.
361
        // Add items to the array on the fly, walking it
362
        // _backwards_ splicing the elements in. The loop definition
363
        // should skip first and last positions.
364
        for ($n=count($elements)-1; $n > 0; $n--) {
365
            array_splice($elements, $n, 0, $separator);
366
        }
367
        return implode('||', $elements);
368
    }
369
 
370
    /**
371
     * Returns the SQL text to be used in order to perform one bitwise XOR operation
372
     * between 2 integers.
373
     *
374
     * @param integer int1 first integer in the operation
375
     * @param integer int2 second integer in the operation
376
     * @return string the piece of SQL code to be used in your statement.
377
     */
378
    public function sql_bitxor($int1, $int2) {
379
        return '( ~' . $this->sql_bitand($int1, $int2) . ' & ' . $this->sql_bitor($int1, $int2) . ')';
380
    }
381
}