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