Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: dbo_sqlite.php 7945 2008-12-19 02:16:01Z gwoo $ */
3
/**
4
 * SQLite layer for DBO
5
 *
6
 * Long description for file
7
 *
8
 * PHP versions 4 and 5
9
 *
10
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
11
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
12
 *
13
 * Licensed under The MIT License
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @filesource
17
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
18
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
19
 * @package       cake
20
 * @subpackage    cake.cake.libs.model.datasources.dbo
21
 * @since         CakePHP(tm) v 0.9.0
22
 * @version       $Revision: 7945 $
23
 * @modifiedby    $LastChangedBy: gwoo $
24
 * @lastmodified  $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $
25
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
26
 */
27
/**
28
 * DBO implementation for the SQLite DBMS.
29
 *
30
 * Long description for class
31
 *
32
 * @package       cake
33
 * @subpackage    cake.cake.libs.model.datasources.dbo
34
 */
35
class DboSqlite extends DboSource {
36
/**
37
 * Enter description here...
38
 *
39
 * @var unknown_type
40
 */
41
	var $description = "SQLite DBO Driver";
42
/**
43
 * Opening quote for quoted identifiers
44
 *
45
 * @var string
46
 */
47
	var $startQuote = '"';
48
/**
49
 * Closing quote for quoted identifiers
50
 *
51
 * @var string
52
 */
53
	var $endQuote = '"';
54
/**
55
 * Keeps the transaction statistics of CREATE/UPDATE/DELETE queries
56
 *
57
 * @var array
58
 * @access protected
59
 */
60
	var $_queryStats = array();
61
/**
62
 * Base configuration settings for SQLite driver
63
 *
64
 * @var array
65
 */
66
	var $_baseConfig = array(
67
		'persistent' => true,
68
		'database' => null,
69
		'connect' => 'sqlite_popen'
70
	);
71
/**
72
 * Index of basic SQL commands
73
 *
74
 * @var array
75
 * @access protected
76
 */
77
	var $_commands = array(
78
		'begin'    => 'BEGIN TRANSACTION',
79
		'commit'   => 'COMMIT TRANSACTION',
80
		'rollback' => 'ROLLBACK TRANSACTION'
81
	);
82
/**
83
 * SQLite column definition
84
 *
85
 * @var array
86
 */
87
	var $columns = array(
88
		'primary_key' => array('name' => 'integer primary key'),
89
		'string' => array('name' => 'varchar', 'limit' => '255'),
90
		'text' => array('name' => 'text'),
91
		'integer' => array('name' => 'integer', 'limit' => null, 'formatter' => 'intval'),
92
		'float' => array('name' => 'float', 'formatter' => 'floatval'),
93
		'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
94
		'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
95
		'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
96
		'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
97
		'binary' => array('name' => 'blob'),
98
		'boolean' => array('name' => 'boolean')
99
	);
100
/**
101
 * Connects to the database using config['database'] as a filename.
102
 *
103
 * @param array $config Configuration array for connecting
104
 * @return mixed
105
 */
106
	function connect() {
107
		$config = $this->config;
108
		$this->connection = $config['connect']($config['database']);
109
		$this->connected = is_resource($this->connection);
110
 
111
		if ($this->connected) {
112
			$this->_execute('PRAGMA count_changes = 1;');
113
		}
114
		return $this->connected;
115
	}
116
/**
117
 * Disconnects from database.
118
 *
119
 * @return boolean True if the database could be disconnected, else false
120
 */
121
	function disconnect() {
122
		@sqlite_close($this->connection);
123
		$this->connected = false;
124
		return $this->connected;
125
	}
126
/**
127
 * Executes given SQL statement.
128
 *
129
 * @param string $sql SQL statement
130
 * @return resource Result resource identifier
131
 */
132
	function _execute($sql) {
133
		$result = sqlite_query($this->connection, $sql);
134
 
135
		if (preg_match('/^(INSERT|UPDATE|DELETE)/', $sql)) {
136
			$this->resultSet($result);
137
			list($this->_queryStats) = $this->fetchResult();
138
		}
139
		return $result;
140
	}
141
/**
142
 * Overrides DboSource::execute() to correctly handle query statistics
143
 *
144
 * @param string $sql
145
 * @return unknown
146
 */
147
	function execute($sql) {
148
		$result = parent::execute($sql);
149
		$this->_queryStats = array();
150
		return $result;
151
	}
152
/**
153
 * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits.
154
 *
155
 * @return array Array of tablenames in the database
156
 */
157
	function listSources() {
158
		$cache = parent::listSources();
159
 
160
		if ($cache != null) {
161
			return $cache;
162
		}
163
		$result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;", false);
164
 
165
		if (empty($result)) {
166
			return array();
167
		} else {
168
			$tables = array();
169
			foreach ($result as $table) {
170
				$tables[] = $table[0]['name'];
171
			}
172
			parent::listSources($tables);
173
			return $tables;
174
		}
175
		return array();
176
	}
177
/**
178
 * Returns an array of the fields in given table name.
179
 *
180
 * @param string $tableName Name of database table to inspect
181
 * @return array Fields in table. Keys are name and type
182
 */
183
	function describe(&$model) {
184
		$cache = parent::describe($model);
185
		if ($cache != null) {
186
			return $cache;
187
		}
188
		$fields = array();
189
		$result = $this->fetchAll('PRAGMA table_info(' . $this->fullTableName($model) . ')');
190
 
191
		foreach ($result as $column) {
192
			$fields[$column[0]['name']] = array(
193
				'type'		=> $this->column($column[0]['type']),
194
				'null'		=> !$column[0]['notnull'],
195
				'default'	=> $column[0]['dflt_value'],
196
				'length'	=> $this->length($column[0]['type'])
197
			);
198
			if ($column[0]['pk'] == 1) {
199
				$fields[$column[0]['name']] = array(
200
					'type'		=> $fields[$column[0]['name']]['type'],
201
					'null'		=> false,
202
					'default'	=> $column[0]['dflt_value'],
203
					'key'		=> $this->index['PRI'],
204
					'length'	=> 11
205
				);
206
			}
207
		}
208
 
209
		$this->__cacheDescription($model->tablePrefix . $model->table, $fields);
210
		return $fields;
211
	}
212
/**
213
 * Returns a quoted and escaped string of $data for use in an SQL statement.
214
 *
215
 * @param string $data String to be prepared for use in an SQL statement
216
 * @return string Quoted and escaped
217
 */
218
	function value($data, $column = null, $safe = false) {
219
		$parent = parent::value($data, $column, $safe);
220
 
221
		if ($parent != null) {
222
			return $parent;
223
		}
224
		if ($data === null) {
225
			return 'NULL';
226
		}
227
		if ($data === '' && $column !== 'integer' && $column !== 'float' && $column !== 'boolean') {
228
			return  "''";
229
		}
230
		switch ($column) {
231
			case 'boolean':
232
				$data = $this->boolean((bool)$data);
233
			break;
234
			case 'integer':
235
			case 'float':
236
				if ($data === '') {
237
					return 'NULL';
238
				}
239
			default:
240
				$data = sqlite_escape_string($data);
241
			break;
242
		}
243
		return "'" . $data . "'";
244
	}
245
/**
246
 * Generates and executes an SQL UPDATE statement for given model, fields, and values.
247
 *
248
 * @param Model $model
249
 * @param array $fields
250
 * @param array $values
251
 * @param mixed $conditions
252
 * @return array
253
 */
254
	function update(&$model, $fields = array(), $values = null, $conditions = null) {
255
		if (empty($values) && !empty($fields)) {
256
			foreach ($fields as $field => $value) {
257
				if (strpos($field, $model->alias . '.') !== false) {
258
					unset($fields[$field]);
259
					$field = str_replace($model->alias . '.', "", $field);
260
					$field = str_replace($model->alias . '.', "", $field);
261
					$fields[$field] = $value;
262
				}
263
			}
264
		}
265
		$result = parent::update($model, $fields, $values, $conditions);
266
		return $result;
267
	}
268
/**
269
 * Deletes all the records in a table and resets the count of the auto-incrementing
270
 * primary key, where applicable.
271
 *
272
 * @param mixed $table A string or model class representing the table to be truncated
273
 * @return boolean	SQL TRUNCATE TABLE statement, false if not applicable.
274
 * @access public
275
 */
276
	function truncate($table) {
277
		return $this->execute('DELETE From ' . $this->fullTableName($table));
278
	}
279
/**
280
 * Returns a formatted error message from previous database operation.
281
 *
282
 * @return string Error message
283
 */
284
	function lastError() {
285
		$error = sqlite_last_error($this->connection);
286
		if ($error) {
287
			return $error.': '.sqlite_error_string($error);
288
		}
289
		return null;
290
	}
291
/**
292
 * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
293
 *
294
 * @return integer Number of affected rows
295
 */
296
	function lastAffected() {
297
		if (!empty($this->_queryStats)) {
298
			foreach (array('rows inserted', 'rows updated', 'rows deleted') as $key) {
299
				if (array_key_exists($key, $this->_queryStats)) {
300
					return $this->_queryStats[$key];
301
				}
302
			}
303
		}
304
		return false;
305
	}
306
/**
307
 * Returns number of rows in previous resultset. If no previous resultset exists,
308
 * this returns false.
309
 *
310
 * @return integer Number of rows in resultset
311
 */
312
	function lastNumRows() {
313
		if ($this->hasResult()) {
314
			sqlite_num_rows($this->_result);
315
		}
316
		return false;
317
	}
318
/**
319
 * Returns the ID generated from the previous INSERT operation.
320
 *
321
 * @return int
322
 */
323
	function lastInsertId() {
324
		return sqlite_last_insert_rowid($this->connection);
325
	}
326
/**
327
 * Converts database-layer column types to basic types
328
 *
329
 * @param string $real Real database-layer column type (i.e. "varchar(255)")
330
 * @return string Abstract column type (i.e. "string")
331
 */
332
	function column($real) {
333
		if (is_array($real)) {
334
			$col = $real['name'];
335
			if (isset($real['limit'])) {
336
				$col .= '('.$real['limit'].')';
337
			}
338
			return $col;
339
		}
340
 
341
		$col = strtolower(str_replace(')', '', $real));
342
		$limit = null;
343
		if (strpos($col, '(') !== false) {
344
			list($col, $limit) = explode('(', $col);
345
		}
346
 
347
		if (in_array($col, array('text', 'integer', 'float', 'boolean', 'timestamp', 'date', 'datetime', 'time'))) {
348
			return $col;
349
		}
350
		if (strpos($col, 'varchar') !== false) {
351
			return 'string';
352
		}
353
		if (in_array($col, array('blob', 'clob'))) {
354
			return 'binary';
355
		}
356
		if (strpos($col, 'numeric') !== false) {
357
			return 'float';
358
		}
359
		return 'text';
360
	}
361
/**
362
 * Enter description here...
363
 *
364
 * @param unknown_type $results
365
 */
366
	function resultSet(&$results) {
367
		$this->results =& $results;
368
		$this->map = array();
369
		$fieldCount = sqlite_num_fields($results);
370
		$index = $j = 0;
371
 
372
		while ($j < $fieldCount) {
373
			$columnName = str_replace('"', '', sqlite_field_name($results, $j));
374
 
375
			if (strpos($columnName, '.')) {
376
				$parts = explode('.', $columnName);
377
				$this->map[$index++] = array($parts[0], $parts[1]);
378
			} else {
379
				$this->map[$index++] = array(0, $columnName);
380
			}
381
			$j++;
382
		}
383
	}
384
/**
385
 * Fetches the next row from the current result set
386
 *
387
 * @return unknown
388
 */
389
	function fetchResult() {
390
		if ($row = sqlite_fetch_array($this->results, SQLITE_ASSOC)) {
391
			$resultRow = array();
392
			$i = 0;
393
 
394
			foreach ($row as $index => $field) {
395
				if (strpos($index, '.')) {
396
					list($table, $column) = explode('.', str_replace('"', '', $index));
397
					$resultRow[$table][$column] = $row[$index];
398
				} else {
399
					$resultRow[0][str_replace('"', '', $index)] = $row[$index];
400
				}
401
				$i++;
402
			}
403
			return $resultRow;
404
		} else {
405
			return false;
406
		}
407
	}
408
/**
409
 * Returns a limit statement in the correct format for the particular database.
410
 *
411
 * @param integer $limit Limit of results returned
412
 * @param integer $offset Offset from which to start results
413
 * @return string SQL limit/offset statement
414
 */
415
	function limit($limit, $offset = null) {
416
		if ($limit) {
417
			$rt = '';
418
			if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
419
				$rt = ' LIMIT';
420
			}
421
			$rt .= ' ' . $limit;
422
			if ($offset) {
423
				$rt .= ' OFFSET ' . $offset;
424
			}
425
			return $rt;
426
		}
427
		return null;
428
	}
429
/**
430
 * Generate a database-native column schema string
431
 *
432
 * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
433
 *                      where options can be 'default', 'length', or 'key'.
434
 * @return string
435
 */
436
	function buildColumn($column) {
437
		$name = $type = null;
438
		$column = array_merge(array('null' => true), $column);
439
		extract($column);
440
 
441
		if (empty($name) || empty($type)) {
442
			trigger_error('Column name or type not defined in schema', E_USER_WARNING);
443
			return null;
444
		}
445
 
446
		if (!isset($this->columns[$type])) {
447
			trigger_error("Column type {$type} does not exist", E_USER_WARNING);
448
			return null;
449
		}
450
 
451
		$real = $this->columns[$type];
452
		if (isset($column['key']) && $column['key'] == 'primary') {
453
			$out = $this->name($name) . ' ' . $this->columns['primary_key']['name'];
454
		} else {
455
			$out = $this->name($name) . ' ' . $real['name'];
456
 
457
			if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
458
				if (isset($column['length'])) {
459
					$length = $column['length'];
460
				} elseif (isset($column['limit'])) {
461
					$length = $column['limit'];
462
				} elseif (isset($real['length'])) {
463
					$length = $real['length'];
464
				} else {
465
					$length = $real['limit'];
466
				}
467
				$out .= '(' . $length . ')';
468
			}
469
			if (isset($column['key']) && $column['key'] == 'primary') {
470
				$out .= ' NOT NULL';
471
			} elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
472
				$out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
473
			} elseif (isset($column['default'])) {
474
				$out .= ' DEFAULT ' . $this->value($column['default'], $type);
475
			} elseif (isset($column['null']) && $column['null'] == true) {
476
				$out .= ' DEFAULT NULL';
477
			} elseif (isset($column['null']) && $column['null'] == false) {
478
				$out .= ' NOT NULL';
479
			}
480
		}
481
		return $out;
482
	}
483
/**
484
 * Sets the database encoding
485
 *
486
 * @param string $enc Database encoding
487
 */
488
	function setEncoding($enc) {
489
		if (!in_array($enc, array("UTF-8", "UTF-16", "UTF-16le", "UTF-16be"))) {
490
			return false;
491
		}
492
		return $this->_execute("PRAGMA encoding = \"{$enc}\"") !== false;
493
	}
494
/**
495
 * Gets the database encoding
496
 *
497
 * @return string The database encoding
498
 */
499
	function getEncoding() {
500
		return $this->fetchRow('PRAGMA encoding');
501
	}
502
/**
503
 * Removes redundant primary key indexes, as they are handled in the column def of the key.
504
 *
505
 * @param array $indexes
506
 * @param string $table
507
 * @return string
508
 */
509
	function buildIndex($indexes, $table = null) {
510
		$join = array();
511
 
512
		foreach ($indexes as $name => $value) {
513
 
514
			if ($name == 'PRIMARY') {
515
				continue;
516
			}
517
			$out = 'CREATE ';
518
 
519
			if (!empty($value['unique'])) {
520
				$out .= 'UNIQUE ';
521
			}
522
			if (is_array($value['column'])) {
523
				$value['column'] = join(', ', array_map(array(&$this, 'name'), $value['column']));
524
			} else {
525
				$value['column'] = $this->name($value['column']);
526
			}
527
			$out .= "INDEX {$name} ON {$table}({$value['column']});";
528
			$join[] = $out;
529
		}
530
		return $join;
531
	}
532
/**
533
 * Overrides DboSource::index to handle SQLite indexe introspection
534
 * Returns an array of the indexes in given table name.
535
 *
536
 * @param string $model Name of model to inspect
537
 * @return array Fields in table. Keys are column and unique
538
 */
539
	function index(&$model) {
540
		$index = array();
541
		$table = $this->fullTableName($model);
542
		if ($table) {
543
			$indexes = $this->query('PRAGMA index_list(' . $table . ')');
544
			$tableInfo = $this->query('PRAGMA table_info(' . $table . ')');
545
			foreach ($indexes as $i => $info) {
546
				$key = array_pop($info);
547
				$keyInfo = $this->query('PRAGMA index_info("' . $key['name'] . '")');
548
				foreach ($keyInfo as $keyCol) {
549
					if (!isset($index[$key['name']])) {
550
						$col = array();
551
						if (preg_match('/autoindex/', $key['name'])) {
552
							$key['name'] = 'PRIMARY';
553
						}
554
						$index[$key['name']]['column'] = $keyCol[0]['name'];
555
						$index[$key['name']]['unique'] = intval($key['unique'] == 1);
556
					} else {
557
						if (!is_array($index[$key['name']]['column'])) {
558
							$col[] = $index[$key['name']]['column'];
559
						}
560
						$col[] = $keyCol[0]['name'];
561
						$index[$key['name']]['column'] = $col;
562
					}
563
				}
564
			}
565
		}
566
		return $index;
567
	}
568
 
569
/**
570
 * Overrides DboSource::renderStatement to handle schema generation with SQLite-style indexes
571
 *
572
 * @param string $type
573
 * @param array $data
574
 * @return string
575
 */
576
	function renderStatement($type, $data) {
577
		switch (strtolower($type)) {
578
			case 'schema':
579
				extract($data);
580
 
581
				foreach (array('columns', 'indexes') as $var) {
582
					if (is_array(${$var})) {
583
						${$var} = "\t" . join(",\n\t", array_filter(${$var}));
584
					}
585
				}
586
				return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
587
			break;
588
			default:
589
				return parent::renderStatement($type, $data);
590
			break;
591
		}
592
	}
593
}
594
?>