dbo_sqlite.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: dbo__sqlite_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 
00004 /**
00005  * SQLite layer for DBO
00006  *
00007  * Long description for file
00008  *
00009  * PHP versions 4 and 5
00010  *
00011  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00012  * Copyright 2005-2008, Cake Software Foundation, Inc.
00013  *                              1785 E. Sahara Avenue, Suite 490-204
00014  *                              Las Vegas, Nevada 89104
00015  *
00016  * Licensed under The MIT License
00017  * Redistributions of files must retain the above copyright notice.
00018  *
00019  * @filesource
00020  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00021  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00022  * @package         cake
00023  * @subpackage      cake.cake.libs.model.datasources.dbo
00024  * @since           CakePHP(tm) v 0.9.0
00025  * @version         $Revision: 580 $
00026  * @modifiedby      $LastChangedBy: gwoo $
00027  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00028  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00029  */
00030 /**
00031  * DBO implementation for the SQLite DBMS.
00032  *
00033  * Long description for class
00034  *
00035  * @package     cake
00036  * @subpackage  cake.cake.libs.model.datasources.dbo
00037  */
00038 class DboSqlite extends DboSource {
00039 
00040 /**
00041  * Enter description here...
00042  *
00043  * @var unknown_type
00044  */
00045     var $description = "SQLite DBO Driver";
00046 /**
00047  * Opening quote for quoted identifiers
00048  *
00049  * @var string
00050  */
00051     var $startQuote = '"';
00052 /**
00053  * Closing quote for quoted identifiers
00054  *
00055  * @var string
00056  */
00057     var $endQuote = '"';
00058 /**
00059  * Keeps the transaction statistics of CREATE/UPDATE/DELETE queries
00060  *
00061  * @var array
00062  * @access protected
00063  */
00064     var $_queryStats = array();
00065 /**
00066  * Base configuration settings for SQLite driver
00067  *
00068  * @var array
00069  */
00070     var $_baseConfig = array(
00071         'persistent' => true,
00072         'database' => null,
00073         'connect' => 'sqlite_popen'
00074     );
00075 /**
00076  * Index of basic SQL commands
00077  *
00078  * @var array
00079  * @access protected
00080  */
00081     var $_commands = array(
00082         'begin'    => 'BEGIN TRANSACTION',
00083         'commit'   => 'COMMIT TRANSACTION',
00084         'rollback' => 'ROLLBACK TRANSACTION'
00085     );
00086 /**
00087  * SQLite column definition
00088  *
00089  * @var array
00090  */
00091     var $columns = array(
00092         'primary_key' => array('name' => 'integer primary key'),
00093         'string' => array('name' => 'varchar', 'limit' => '255'),
00094         'text' => array('name' => 'text'),
00095         'integer' => array('name' => 'integer', 'limit' => null, 'formatter' => 'intval'),
00096         'float' => array('name' => 'float', 'formatter' => 'floatval'),
00097         'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
00098         'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
00099         'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
00100         'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
00101         'binary' => array('name' => 'blob'),
00102         'boolean' => array('name' => 'boolean')
00103     );
00104 /**
00105  * Connects to the database using config['database'] as a filename.
00106  *
00107  * @param array $config Configuration array for connecting
00108  * @return mixed
00109  */
00110     function connect() {
00111         $config = $this->config;
00112         $this->connection = $config['connect']($config['database']);
00113         $this->connected = is_resource($this->connection);
00114 
00115         if ($this->connected) {
00116             $this->_execute('PRAGMA count_changes = 1;');
00117         }
00118         return $this->connected;
00119     }
00120 /**
00121  * Disconnects from database.
00122  *
00123  * @return boolean True if the database could be disconnected, else false
00124  */
00125     function disconnect() {
00126         @sqlite_close($this->connection);
00127         $this->connected = false;
00128         return $this->connected;
00129     }
00130 /**
00131  * Executes given SQL statement.
00132  *
00133  * @param string $sql SQL statement
00134  * @return resource Result resource identifier
00135  */
00136     function _execute($sql) {
00137         $result = sqlite_query($this->connection, $sql);
00138 
00139         if (preg_match('/^(INSERT|UPDATE|DELETE)/', $sql)) {
00140             $this->resultSet($result);
00141             list($this->_queryStats) = $this->fetchResult();
00142         }
00143         return $result;
00144     }
00145 /**
00146  * Overrides DboSource::execute() to correctly handle query statistics
00147  *
00148  * @param string $sql
00149  * @return unknown
00150  */
00151     function execute($sql) {
00152         $result = parent::execute($sql);
00153         $this->_queryStats = array();
00154         return $result;
00155     }
00156 /**
00157  * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits.
00158  *
00159  * @return array Array of tablenames in the database
00160  */
00161     function listSources() {
00162         $db = $this->config['database'];
00163         $this->config['database'] = basename($this->config['database']);
00164 
00165         $cache = parent::listSources();
00166         if ($cache != null) {
00167             return $cache;
00168         }
00169 
00170         $result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
00171 
00172         if (!$result || empty($result)) {
00173             return array();
00174         } else {
00175             $tables = array();
00176             foreach ($result as $table) {
00177                 $tables[] = $table[0]['name'];
00178             }
00179             parent::listSources($tables);
00180 
00181             $this->config['database'] = $db;
00182             return $tables;
00183         }
00184         $this->config['database'] = $db;
00185         return array();
00186     }
00187 /**
00188  * Returns an array of the fields in given table name.
00189  *
00190  * @param string $tableName Name of database table to inspect
00191  * @return array Fields in table. Keys are name and type
00192  */
00193     function describe(&$model) {
00194         $cache = parent::describe($model);
00195         if ($cache != null) {
00196             return $cache;
00197         }
00198         $fields = array();
00199         $result = $this->fetchAll('PRAGMA table_info(' . $this->fullTableName($model) . ')');
00200 
00201         foreach ($result as $column) {
00202             $fields[$column[0]['name']] = array(
00203                 'type'      => $this->column($column[0]['type']),
00204                 'null'      => !$column[0]['notnull'],
00205                 'default'   => $column[0]['dflt_value'],
00206                 'length'    => $this->length($column[0]['type'])
00207             );
00208             if($column[0]['pk'] == 1) {
00209                 $fields[$column[0]['name']] = array(
00210                     'type'      => $fields[$column[0]['name']]['type'],
00211                     'null'      => false,
00212                     'default'   => $column[0]['dflt_value'],
00213                     'key'       => $this->index['PRI'],
00214                     'length'    => 11
00215                 );
00216             }
00217         }
00218 
00219         $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
00220         return $fields;
00221     }
00222 /**
00223  * Returns a quoted and escaped string of $data for use in an SQL statement.
00224  *
00225  * @param string $data String to be prepared for use in an SQL statement
00226  * @return string Quoted and escaped
00227  */
00228     function value($data, $column = null, $safe = false) {
00229         $parent = parent::value($data, $column, $safe);
00230 
00231         if ($parent != null) {
00232             return $parent;
00233         }
00234         if ($data === null) {
00235             return 'NULL';
00236         }
00237         if ($data === '') {
00238             return  "''";
00239         }
00240         switch ($column) {
00241             case 'boolean':
00242                 $data = $this->boolean((bool)$data);
00243             break;
00244             default:
00245                 $data = sqlite_escape_string($data);
00246             break;
00247         }
00248         return "'" . $data . "'";
00249     }
00250 /**
00251  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
00252  *
00253  * @param Model $model
00254  * @param array $fields
00255  * @param array $values
00256  * @param mixed $conditions
00257  * @return array
00258  */
00259     function update(&$model, $fields = array(), $values = null, $conditions = null) {
00260         if (empty($values) && !empty($fields)) {
00261             foreach ($fields as $field => $value) {
00262                 if (strpos($field, $model->alias . '.') !== false) {
00263                     unset($fields[$field]);
00264                     $field = str_replace($model->alias . '.', "", $field);
00265                     $field = str_replace($model->alias . '.', "", $field);
00266                     $fields[$field] = $value;
00267                 }
00268             }
00269         }
00270         $result = parent::update($model, $fields, $values, $conditions);
00271         return $result;
00272     }
00273 /**
00274  * Deletes all the records in a table and resets the count of the auto-incrementing
00275  * primary key, where applicable.
00276  *
00277  * @param mixed $table A string or model class representing the table to be truncated
00278  * @return boolean  SQL TRUNCATE TABLE statement, false if not applicable.
00279  * @access public
00280  */
00281     function truncate($table) {
00282         return $this->execute('DELETE From ' . $this->fullTableName($table));
00283     }
00284 /**
00285  * Returns a formatted error message from previous database operation.
00286  *
00287  * @return string Error message
00288  */
00289     function lastError() {
00290         $error = sqlite_last_error($this->connection);
00291         if ($error) {
00292             return $error.': '.sqlite_error_string($error);
00293         }
00294         return null;
00295     }
00296 /**
00297  * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
00298  *
00299  * @return integer Number of affected rows
00300  */
00301     function lastAffected() {
00302         if (!empty($this->_queryStats)) {
00303             foreach (array('rows inserted', 'rows updated', 'rows deleted') as $key) {
00304                 if (array_key_exists($key, $this->_queryStats)) {
00305                     return $this->_queryStats[$key];
00306                 }
00307             }
00308         }
00309         return false;
00310     }
00311 /**
00312  * Returns number of rows in previous resultset. If no previous resultset exists,
00313  * this returns false.
00314  *
00315  * @return integer Number of rows in resultset
00316  */
00317     function lastNumRows() {
00318         if ($this->hasResult()) {
00319             sqlite_num_rows($this->_result);
00320         }
00321         return false;
00322     }
00323 /**
00324  * Returns the ID generated from the previous INSERT operation.
00325  *
00326  * @return int
00327  */
00328     function lastInsertId() {
00329         return sqlite_last_insert_rowid($this->connection);
00330     }
00331 /**
00332  * Converts database-layer column types to basic types
00333  *
00334  * @param string $real Real database-layer column type (i.e. "varchar(255)")
00335  * @return string Abstract column type (i.e. "string")
00336  */
00337     function column($real) {
00338         if (is_array($real)) {
00339             $col = $real['name'];
00340             if (isset($real['limit'])) {
00341                 $col .= '('.$real['limit'].')';
00342             }
00343             return $col;
00344         }
00345 
00346         $col = strtolower(str_replace(')', '', $real));
00347         $limit = null;
00348         if (strpos($col, '(') !== false) {
00349             list($col, $limit) = explode('(', $col);
00350         }
00351 
00352         if (in_array($col, array('text', 'integer', 'float', 'boolean', 'timestamp', 'date', 'datetime', 'time'))) {
00353             return $col;
00354         }
00355         if (strpos($col, 'varchar') !== false) {
00356             return 'string';
00357         }
00358         if (in_array($col, array('blob', 'clob'))) {
00359             return 'binary';
00360         }
00361         if (strpos($col, 'numeric') !== false) {
00362             return 'float';
00363         }
00364         return 'text';
00365     }
00366 /**
00367  * Enter description here...
00368  *
00369  * @param unknown_type $results
00370  */
00371     function resultSet(&$results) {
00372         $this->results =& $results;
00373         $this->map = array();
00374         $fieldCount = sqlite_num_fields($results);
00375         $index = $j = 0;
00376 
00377         while ($j < $fieldCount) {
00378             $columnName = str_replace('"', '', sqlite_field_name($results, $j));
00379 
00380             if (strpos($columnName, '.')) {
00381                 $parts = explode('.', $columnName);
00382                 $this->map[$index++] = array($parts[0], $parts[1]);
00383             } else {
00384                 $this->map[$index++] = array(0, $columnName);
00385             }
00386             $j++;
00387         }
00388     }
00389 /**
00390  * Fetches the next row from the current result set
00391  *
00392  * @return unknown
00393  */
00394     function fetchResult() {
00395         if ($row = sqlite_fetch_array($this->results, SQLITE_ASSOC)) {
00396             $resultRow = array();
00397             $i = 0;
00398 
00399             foreach ($row as $index => $field) {
00400                 if (strpos($index, '.')) {
00401                     list($table, $column) = explode('.', str_replace('"', '', $index));
00402                     $resultRow[$table][$column] = $row[$index];
00403                 } else {
00404                     $resultRow[0][str_replace('"', '', $index)] = $row[$index];
00405                 }
00406                 $i++;
00407             }
00408             return $resultRow;
00409         } else {
00410             return false;
00411         }
00412     }
00413 /**
00414  * Returns a limit statement in the correct format for the particular database.
00415  *
00416  * @param integer $limit Limit of results returned
00417  * @param integer $offset Offset from which to start results
00418  * @return string SQL limit/offset statement
00419  */
00420     function limit ($limit, $offset = null) {
00421         if ($limit) {
00422             $rt = '';
00423             if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
00424                 $rt = ' LIMIT';
00425             }
00426             $rt .= ' ' . $limit;
00427             if ($offset) {
00428                 $rt .= ' OFFSET ' . $offset;
00429             }
00430             return $rt;
00431         }
00432         return null;
00433     }
00434 /**
00435  * Generate a database-native column schema string
00436  *
00437  * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
00438  *                      where options can be 'default', 'length', or 'key'.
00439  * @return string
00440  */
00441     function buildColumn($column) {
00442         $name = $type = null;
00443         $column = array_merge(array('null' => true), $column);
00444         extract($column);
00445 
00446         if (empty($name) || empty($type)) {
00447             trigger_error('Column name or type not defined in schema', E_USER_WARNING);
00448             return null;
00449         }
00450 
00451         if (!isset($this->columns[$type])) {
00452             trigger_error("Column type {$type} does not exist", E_USER_WARNING);
00453             return null;
00454         }
00455 
00456         $real = $this->columns[$type];
00457         if (isset($column['key']) && $column['key'] == 'primary') {
00458             $out = $this->name($name) . ' ' . $this->columns['primary_key']['name'];
00459         } else {
00460             $out = $this->name($name) . ' ' . $real['name'];
00461 
00462             if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
00463                 if (isset($column['length'])) {
00464                     $length = $column['length'];
00465                 } elseif (isset($column['limit'])) {
00466                     $length = $column['limit'];
00467                 } elseif (isset($real['length'])) {
00468                     $length = $real['length'];
00469                 } else {
00470                     $length = $real['limit'];
00471                 }
00472                 $out .= '(' . $length . ')';
00473             }
00474             if (isset($column['key']) && $column['key'] == 'primary') {
00475                 $out .= ' NOT NULL';
00476             } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
00477                 $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
00478             } elseif (isset($column['default'])) {
00479                 $out .= ' DEFAULT ' . $this->value($column['default'], $type);
00480             } elseif (isset($column['null']) && $column['null'] == true) {
00481                 $out .= ' DEFAULT NULL';
00482             } elseif (isset($column['null']) && $column['null'] == false) {
00483                 $out .= ' NOT NULL';
00484             }
00485         }
00486         return $out;
00487     }
00488 /**
00489  * Sets the database encoding
00490  *
00491  * @param string $enc Database encoding
00492  */
00493     function setEncoding($enc) {
00494         if (!in_array($enc, array("UTF-8", "UTF-16", "UTF-16le", "UTF-16be"))) {
00495             return false;
00496         }
00497         return $this->_execute("PRAGMA encoding = \"{$enc}\"") !== false;
00498     }
00499 /**
00500  * Gets the database encoding
00501  *
00502  * @return string The database encoding
00503  */
00504     function getEncoding() {
00505         return $this->fetchRow('PRAGMA encoding');
00506     }
00507 /**
00508  * Removes redundant primary key indexes, as they are handled in the column def of the key.
00509  *
00510  * @param array $indexes
00511  * @param string $table
00512  * @return string
00513  */
00514     function buildIndex($indexes, $table = null) {
00515         $join = array();
00516 
00517         foreach ($indexes as $name => $value) {
00518 
00519             if ($name == 'PRIMARY') {
00520                 continue;
00521             }
00522             $out = 'CREATE ';
00523 
00524             if (!empty($value['unique'])) {
00525                 $out .= 'UNIQUE ';
00526             }
00527             if (is_array($value['column'])) {
00528                 $value['column'] = join(', ', array_map(array(&$this, 'name'), $value['column']));
00529             } else {
00530                 $value['column'] = $this->name($value['column']);
00531             }
00532             $out .= "INDEX {$name} ON {$table}({$value['column']});";
00533             $join[] = $out;
00534         }
00535         return $join;
00536     }
00537 /**
00538  * Overrides DboSource::renderStatement to handle schema generation with SQLite-style indexes
00539  *
00540  * @param string $type
00541  * @param array $data
00542  * @return string
00543  */
00544     function renderStatement($type, $data) {
00545         switch (strtolower($type)) {
00546             case 'schema':
00547                 extract($data);
00548 
00549                 foreach (array('columns', 'indexes') as $var) {
00550                     if (is_array(${$var})) {
00551                         ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
00552                     }
00553                 }
00554                 return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
00555             break;
00556             default:
00557                 return parent::renderStatement($type, $data);
00558             break;
00559         }
00560     }
00561 }
00562 
00563 ?>