Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 1
<?php
2
/**
3
 * SQLite layer for DBO
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.Model.Datasource.Database
15
 * @since         CakePHP(tm) v 0.9.0
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18
 
19
App::uses('DboSource', 'Model/Datasource');
20
App::uses('String', 'Utility');
21
 
22
/**
23
 * DBO implementation for the SQLite3 DBMS.
24
 *
25
 * A DboSource adapter for SQLite 3 using PDO
26
 *
27
 * @package       Cake.Model.Datasource.Database
28
 */
29
class Sqlite extends DboSource {
30
 
31
/**
32
 * Datasource Description
33
 *
34
 * @var string
35
 */
36
	public $description = "SQLite DBO Driver";
37
 
38
/**
39
 * Quote Start
40
 *
41
 * @var string
42
 */
43
	public $startQuote = '"';
44
 
45
/**
46
 * Quote End
47
 *
48
 * @var string
49
 */
50
	public $endQuote = '"';
51
 
52
/**
53
 * Base configuration settings for SQLite3 driver
54
 *
55
 * @var array
56
 */
57
	protected $_baseConfig = array(
58
		'persistent' => false,
59
		'database' => null
60
	);
61
 
62
/**
63
 * SQLite3 column definition
64
 *
65
 * @var array
66
 */
67
	public $columns = array(
68
		'primary_key' => array('name' => 'integer primary key autoincrement'),
69
		'string' => array('name' => 'varchar', 'limit' => '255'),
70
		'text' => array('name' => 'text'),
71
		'integer' => array('name' => 'integer', 'limit' => null, 'formatter' => 'intval'),
72
		'biginteger' => array('name' => 'bigint', 'limit' => 20),
73
		'float' => array('name' => 'float', 'formatter' => 'floatval'),
74
		'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
75
		'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
76
		'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
77
		'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
78
		'binary' => array('name' => 'blob'),
79
		'boolean' => array('name' => 'boolean')
80
	);
81
 
82
/**
83
 * List of engine specific additional field parameters used on table creating
84
 *
85
 * @var array
86
 */
87
	public $fieldParameters = array(
88
		'collate' => array(
89
			'value' => 'COLLATE',
90
			'quote' => false,
91
			'join' => ' ',
92
			'column' => 'Collate',
93
			'position' => 'afterDefault',
94
			'options' => array(
95
				'BINARY', 'NOCASE', 'RTRIM'
96
			)
97
		),
98
	);
99
 
100
/**
101
 * Connects to the database using config['database'] as a filename.
102
 *
103
 * @return boolean
104
 * @throws MissingConnectionException
105
 */
106
	public function connect() {
107
		$config = $this->config;
108
		$flags = array(
109
			PDO::ATTR_PERSISTENT => $config['persistent'],
110
			PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
111
		);
112
		try {
113
			$this->_connection = new PDO('sqlite:' . $config['database'], null, null, $flags);
114
			$this->connected = true;
115
		} catch(PDOException $e) {
116
			throw new MissingConnectionException(array(
117
				'class' => get_class($this),
118
				'message' => $e->getMessage()
119
			));
120
		}
121
		return $this->connected;
122
	}
123
 
124
/**
125
 * Check whether the SQLite extension is installed/loaded
126
 *
127
 * @return boolean
128
 */
129
	public function enabled() {
130
		return in_array('sqlite', PDO::getAvailableDrivers());
131
	}
132
 
133
/**
134
 * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits.
135
 *
136
 * @param mixed $data
137
 * @return array Array of table names in the database
138
 */
139
	public function listSources($data = null) {
140
		$cache = parent::listSources();
141
		if ($cache) {
142
			return $cache;
143
		}
144
 
145
		$result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;", false);
146
 
147
		if (!$result || empty($result)) {
148
			return array();
149
		}
150
 
151
		$tables = array();
152
		foreach ($result as $table) {
153
			$tables[] = $table[0]['name'];
154
		}
155
		parent::listSources($tables);
156
		return $tables;
157
	}
158
 
159
/**
160
 * Returns an array of the fields in given table name.
161
 *
162
 * @param Model|string $model Either the model or table name you want described.
163
 * @return array Fields in table. Keys are name and type
164
 */
165
	public function describe($model) {
166
		$table = $this->fullTableName($model, false, false);
167
		$cache = parent::describe($table);
168
		if ($cache) {
169
			return $cache;
170
		}
171
		$fields = array();
172
		$result = $this->_execute(
173
			'PRAGMA table_info(' . $this->value($table, 'string') . ')'
174
		);
175
 
176
		foreach ($result as $column) {
177
			$column = (array)$column;
178
			$default = ($column['dflt_value'] === 'NULL') ? null : trim($column['dflt_value'], "'");
179
 
180
			$fields[$column['name']] = array(
181
				'type' => $this->column($column['type']),
182
				'null' => !$column['notnull'],
183
				'default' => $default,
184
				'length' => $this->length($column['type'])
185
			);
186
			if ($column['pk'] == 1) {
187
				$fields[$column['name']]['key'] = $this->index['PRI'];
188
				$fields[$column['name']]['null'] = false;
189
				if (empty($fields[$column['name']]['length'])) {
190
					$fields[$column['name']]['length'] = 11;
191
				}
192
			}
193
		}
194
 
195
		$result->closeCursor();
196
		$this->_cacheDescription($table, $fields);
197
		return $fields;
198
	}
199
 
200
/**
201
 * Generates and executes an SQL UPDATE statement for given model, fields, and values.
202
 *
203
 * @param Model $model
204
 * @param array $fields
205
 * @param array $values
206
 * @param mixed $conditions
207
 * @return array
208
 */
209
	public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
210
		if (empty($values) && !empty($fields)) {
211
			foreach ($fields as $field => $value) {
212
				if (strpos($field, $model->alias . '.') !== false) {
213
					unset($fields[$field]);
214
					$field = str_replace($model->alias . '.', "", $field);
215
					$field = str_replace($model->alias . '.', "", $field);
216
					$fields[$field] = $value;
217
				}
218
			}
219
		}
220
		return parent::update($model, $fields, $values, $conditions);
221
	}
222
 
223
/**
224
 * Deletes all the records in a table and resets the count of the auto-incrementing
225
 * primary key, where applicable.
226
 *
227
 * @param string|Model $table A string or model class representing the table to be truncated
228
 * @return boolean SQL TRUNCATE TABLE statement, false if not applicable.
229
 */
230
	public function truncate($table) {
231
		if (in_array('sqlite_sequence', $this->listSources())) {
232
			$this->_execute('DELETE FROM sqlite_sequence where name=' . $this->startQuote . $this->fullTableName($table, false, false) . $this->endQuote);
233
		}
234
		return $this->execute('DELETE FROM ' . $this->fullTableName($table));
235
	}
236
 
237
/**
238
 * Converts database-layer column types to basic types
239
 *
240
 * @param string $real Real database-layer column type (i.e. "varchar(255)")
241
 * @return string Abstract column type (i.e. "string")
242
 */
243
	public function column($real) {
244
		if (is_array($real)) {
245
			$col = $real['name'];
246
			if (isset($real['limit'])) {
247
				$col .= '(' . $real['limit'] . ')';
248
			}
249
			return $col;
250
		}
251
 
252
		$col = strtolower(str_replace(')', '', $real));
253
		$limit = null;
254
		if (strpos($col, '(') !== false) {
255
			list($col, $limit) = explode('(', $col);
256
		}
257
 
258
		$standard = array(
259
			'text',
260
			'integer',
261
			'float',
262
			'boolean',
263
			'timestamp',
264
			'date',
265
			'datetime',
266
			'time'
267
		);
268
		if (in_array($col, $standard)) {
269
			return $col;
270
		}
271
		if ($col === 'bigint') {
272
			return 'biginteger';
273
		}
274
		if (strpos($col, 'char') !== false) {
275
			return 'string';
276
		}
277
		if (in_array($col, array('blob', 'clob'))) {
278
			return 'binary';
279
		}
280
		if (strpos($col, 'numeric') !== false || strpos($col, 'decimal') !== false) {
281
			return 'float';
282
		}
283
		return 'text';
284
	}
285
 
286
/**
287
 * Generate ResultSet
288
 *
289
 * @param mixed $results
290
 * @return void
291
 */
292
	public function resultSet($results) {
293
		$this->results = $results;
294
		$this->map = array();
295
		$numFields = $results->columnCount();
296
		$index = 0;
297
		$j = 0;
298
 
299
		//PDO::getColumnMeta is experimental and does not work with sqlite3,
300
		//	so try to figure it out based on the querystring
301
		$querystring = $results->queryString;
302
		if (stripos($querystring, 'SELECT') === 0) {
303
			$last = strripos($querystring, 'FROM');
304
			if ($last !== false) {
305
				$selectpart = substr($querystring, 7, $last - 8);
306
				$selects = String::tokenize($selectpart, ',', '(', ')');
307
			}
308
		} elseif (strpos($querystring, 'PRAGMA table_info') === 0) {
309
			$selects = array('cid', 'name', 'type', 'notnull', 'dflt_value', 'pk');
310
		} elseif (strpos($querystring, 'PRAGMA index_list') === 0) {
311
			$selects = array('seq', 'name', 'unique');
312
		} elseif (strpos($querystring, 'PRAGMA index_info') === 0) {
313
			$selects = array('seqno', 'cid', 'name');
314
		}
315
		while ($j < $numFields) {
316
			if (!isset($selects[$j])) {
317
				$j++;
318
				continue;
319
			}
320
			if (preg_match('/\bAS\s+(.*)/i', $selects[$j], $matches)) {
321
				$columnName = trim($matches[1], '"');
322
			} else {
323
				$columnName = trim(str_replace('"', '', $selects[$j]));
324
			}
325
 
326
			if (strpos($selects[$j], 'DISTINCT') === 0) {
327
				$columnName = str_ireplace('DISTINCT', '', $columnName);
328
			}
329
 
330
			$metaType = false;
331
			try {
332
				$metaData = (array)$results->getColumnMeta($j);
333
				if (!empty($metaData['sqlite:decl_type'])) {
334
					$metaType = trim($metaData['sqlite:decl_type']);
335
				}
336
			} catch (Exception $e) {
337
			}
338
 
339
			if (strpos($columnName, '.')) {
340
				$parts = explode('.', $columnName);
341
				$this->map[$index++] = array(trim($parts[0]), trim($parts[1]), $metaType);
342
			} else {
343
				$this->map[$index++] = array(0, $columnName, $metaType);
344
			}
345
			$j++;
346
		}
347
	}
348
 
349
/**
350
 * Fetches the next row from the current result set
351
 *
352
 * @return mixed array with results fetched and mapped to column names or false if there is no results left to fetch
353
 */
354
	public function fetchResult() {
355
		if ($row = $this->_result->fetch(PDO::FETCH_NUM)) {
356
			$resultRow = array();
357
			foreach ($this->map as $col => $meta) {
358
				list($table, $column, $type) = $meta;
359
				$resultRow[$table][$column] = $row[$col];
360
				if ($type === 'boolean' && $row[$col] !== null) {
361
					$resultRow[$table][$column] = $this->boolean($resultRow[$table][$column]);
362
				}
363
			}
364
			return $resultRow;
365
		}
366
		$this->_result->closeCursor();
367
		return false;
368
	}
369
 
370
/**
371
 * Returns a limit statement in the correct format for the particular database.
372
 *
373
 * @param integer $limit Limit of results returned
374
 * @param integer $offset Offset from which to start results
375
 * @return string SQL limit/offset statement
376
 */
377
	public function limit($limit, $offset = null) {
378
		if ($limit) {
379
			$rt = sprintf(' LIMIT %u', $limit);
380
			if ($offset) {
381
				$rt .= sprintf(' OFFSET %u', $offset);
382
			}
383
			return $rt;
384
		}
385
		return null;
386
	}
387
 
388
/**
389
 * Generate a database-native column schema string
390
 *
391
 * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
392
 *    where options can be 'default', 'length', or 'key'.
393
 * @return string
394
 */
395
	public function buildColumn($column) {
396
		$name = $type = null;
397
		$column = array_merge(array('null' => true), $column);
398
		extract($column);
399
 
400
		if (empty($name) || empty($type)) {
401
			trigger_error(__d('cake_dev', 'Column name or type not defined in schema'), E_USER_WARNING);
402
			return null;
403
		}
404
 
405
		if (!isset($this->columns[$type])) {
406
			trigger_error(__d('cake_dev', 'Column type %s does not exist', $type), E_USER_WARNING);
407
			return null;
408
		}
409
 
410
		$isPrimary = (isset($column['key']) && $column['key'] === 'primary');
411
		if ($isPrimary && $type === 'integer') {
412
			return $this->name($name) . ' ' . $this->columns['primary_key']['name'];
413
		}
414
		$out = parent::buildColumn($column);
415
		if ($isPrimary && $type === 'biginteger') {
416
			$replacement = 'PRIMARY KEY';
417
			if ($column['null'] === false) {
418
				$replacement = 'NOT NULL ' . $replacement;
419
			}
420
			return str_replace($this->columns['primary_key']['name'], $replacement, $out);
421
		}
422
		return $out;
423
	}
424
 
425
/**
426
 * Sets the database encoding
427
 *
428
 * @param string $enc Database encoding
429
 * @return boolean
430
 */
431
	public function setEncoding($enc) {
432
		if (!in_array($enc, array("UTF-8", "UTF-16", "UTF-16le", "UTF-16be"))) {
433
			return false;
434
		}
435
		return $this->_execute("PRAGMA encoding = \"{$enc}\"") !== false;
436
	}
437
 
438
/**
439
 * Gets the database encoding
440
 *
441
 * @return string The database encoding
442
 */
443
	public function getEncoding() {
444
		return $this->fetchRow('PRAGMA encoding');
445
	}
446
 
447
/**
448
 * Removes redundant primary key indexes, as they are handled in the column def of the key.
449
 *
450
 * @param array $indexes
451
 * @param string $table
452
 * @return string
453
 */
454
	public function buildIndex($indexes, $table = null) {
455
		$join = array();
456
 
457
		$table = str_replace('"', '', $table);
458
		list($dbname, $table) = explode('.', $table);
459
		$dbname = $this->name($dbname);
460
 
461
		foreach ($indexes as $name => $value) {
462
 
463
			if ($name === 'PRIMARY') {
464
				continue;
465
			}
466
			$out = 'CREATE ';
467
 
468
			if (!empty($value['unique'])) {
469
				$out .= 'UNIQUE ';
470
			}
471
			if (is_array($value['column'])) {
472
				$value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column']));
473
			} else {
474
				$value['column'] = $this->name($value['column']);
475
			}
476
			$t = trim($table, '"');
477
			$indexname = $this->name($t . '_' . $name);
478
			$table = $this->name($table);
479
			$out .= "INDEX {$dbname}.{$indexname} ON {$table}({$value['column']});";
480
			$join[] = $out;
481
		}
482
		return $join;
483
	}
484
 
485
/**
486
 * Overrides DboSource::index to handle SQLite index introspection
487
 * Returns an array of the indexes in given table name.
488
 *
489
 * @param string $model Name of model to inspect
490
 * @return array Fields in table. Keys are column and unique
491
 */
492
	public function index($model) {
493
		$index = array();
494
		$table = $this->fullTableName($model, false, false);
495
		if ($table) {
496
			$indexes = $this->query('PRAGMA index_list(' . $table . ')');
497
 
498
			if (is_bool($indexes)) {
499
				return array();
500
			}
501
			foreach ($indexes as $info) {
502
				$key = array_pop($info);
503
				$keyInfo = $this->query('PRAGMA index_info("' . $key['name'] . '")');
504
				foreach ($keyInfo as $keyCol) {
505
					if (!isset($index[$key['name']])) {
506
						$col = array();
507
						if (preg_match('/autoindex/', $key['name'])) {
508
							$key['name'] = 'PRIMARY';
509
						}
510
						$index[$key['name']]['column'] = $keyCol[0]['name'];
511
						$index[$key['name']]['unique'] = intval($key['unique'] == 1);
512
					} else {
513
						if (!is_array($index[$key['name']]['column'])) {
514
							$col[] = $index[$key['name']]['column'];
515
						}
516
						$col[] = $keyCol[0]['name'];
517
						$index[$key['name']]['column'] = $col;
518
					}
519
				}
520
			}
521
		}
522
		return $index;
523
	}
524
 
525
/**
526
 * Overrides DboSource::renderStatement to handle schema generation with SQLite-style indexes
527
 *
528
 * @param string $type
529
 * @param array $data
530
 * @return string
531
 */
532
	public function renderStatement($type, $data) {
533
		switch (strtolower($type)) {
534
			case 'schema':
535
				extract($data);
536
				if (is_array($columns)) {
537
					$columns = "\t" . implode(",\n\t", array_filter($columns));
538
				}
539
				if (is_array($indexes)) {
540
					$indexes = "\t" . implode("\n\t", array_filter($indexes));
541
				}
542
				return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
543
			default:
544
				return parent::renderStatement($type, $data);
545
		}
546
	}
547
 
548
/**
549
 * PDO deals in objects, not resources, so overload accordingly.
550
 *
551
 * @return boolean
552
 */
553
	public function hasResult() {
554
		return is_object($this->_result);
555
	}
556
 
557
/**
558
 * Generate a "drop table" statement for the given table
559
 *
560
 * @param type $table Name of the table to drop
561
 * @return string Drop table SQL statement
562
 */
563
	protected function _dropTable($table) {
564
		return 'DROP TABLE IF EXISTS ' . $this->fullTableName($table) . ";";
565
	}
566
 
567
/**
568
 * Gets the schema name
569
 *
570
 * @return string The schema name
571
 */
572
	public function getSchemaName() {
573
		return "main"; // Sqlite Datasource does not support multidb
574
	}
575
 
576
/**
577
 * Check if the server support nested transactions
578
 *
579
 * @return boolean
580
 */
581
	public function nestedTransactionSupported() {
582
		return $this->useNestedTransactions && version_compare($this->getVersion(), '3.6.8', '>=');
583
	}
584
 
585
}