]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/sqlite.php
Activated Id substitution for Subversion
[SourceForge/phpwiki.git] / lib / pear / DB / sqlite.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2004 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Urs Gehrig <urs@circle.ch>                                  |
17 // |          Mika Tuupola <tuupola@appelsiini.net>                       |
18 // | Maintainer: Daniel Convissor <danielc@php.net>                       |
19 // +----------------------------------------------------------------------+
20 //
21 // $Id$
22
23 require_once 'DB/common.php';
24
25 /**
26  * Database independent query interface definition for the SQLite
27  * PECL extension.
28  *
29  * @package  DB
30  * @version  $Id$
31  * @category Database
32  * @author   Urs Gehrig <urs@circle.ch>
33  * @author   Mika Tuupola <tuupola@appelsiini.net>
34  */
35 class DB_sqlite extends DB_common
36 {
37     // {{{ properties
38
39     var $connection;
40     var $phptype, $dbsyntax;
41     var $prepare_tokens = array();
42     var $prepare_types = array();
43     var $_lasterror = '';
44
45     // }}}
46     // {{{ constructor
47
48     /**
49      * Constructor for this class.
50      *
51      * Error codes according to sqlite_exec.  Error Codes specification is
52      * in the {@link http://sqlite.org/c_interface.html online manual}.
53      *
54      * This errorhandling based on sqlite_exec is not yet implemented.
55      *
56      * @access public
57      */
58     function DB_sqlite()
59     {
60
61         $this->DB_common();
62         $this->phptype = 'sqlite';
63         $this->dbsyntax = 'sqlite';
64         $this->features = array (
65             'prepare' => false,
66             'pconnect' => true,
67             'transactions' => false,
68             'limit' => 'alter'
69         );
70
71         // SQLite data types, http://www.sqlite.org/datatypes.html
72         $this->keywords = array (
73             'BLOB'      => '',
74             'BOOLEAN'   => '',
75             'CHARACTER' => '',
76             'CLOB'      => '',
77             'FLOAT'     => '',
78             'INTEGER'   => '',
79             'KEY'       => '',
80             'NATIONAL'  => '',
81             'NUMERIC'   => '',
82             'NVARCHAR'  => '',
83             'PRIMARY'   => '',
84             'TEXT'      => '',
85             'TIMESTAMP' => '',
86             'UNIQUE'    => '',
87             'VARCHAR'   => '',
88             'VARYING'   => ''
89         );
90         $this->errorcode_map = array(
91         );
92     }
93
94     // }}}
95     // {{{ connect()
96
97     /**
98      * Connect to a database represented by a file.
99      *
100      * @param $dsn the data source name; the file is taken as
101      *        database; "sqlite://root:@host/test.db?mode=0644"
102      * @param $persistent (optional) whether the connection should
103      *        be persistent
104      * @access public
105      * @return int DB_OK on success, a DB error on failure
106      */
107     function connect($dsninfo, $persistent = false)
108     {
109         if (!DB::assertExtension('sqlite')) {
110             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
111         }
112
113         $this->dsn = $dsninfo;
114
115         if ($dsninfo['database']) {
116             if (!file_exists($dsninfo['database'])) {
117                 if (!touch($dsninfo['database'])) {
118                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
119                 }
120                 if (!isset($dsninfo['mode']) ||
121                     !is_numeric($dsninfo['mode']))
122                 {
123                     $mode = 0644;
124                 } else {
125                     $mode = octdec($dsninfo['mode']);
126                 }
127                 if (!chmod($dsninfo['database'], $mode)) {
128                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
129                 }
130                 if (!file_exists($dsninfo['database'])) {
131                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
132                 }
133             }
134             if (!is_file($dsninfo['database'])) {
135                 return $this->sqliteRaiseError(DB_ERROR_INVALID);
136             }
137             if (!is_readable($dsninfo['database'])) {
138                 return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
139             }
140         } else {
141             return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
142         }
143
144         $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
145         if (!($conn = @$connect_function($dsninfo['database']))) {
146             return $this->sqliteRaiseError(DB_ERROR_NODBSELECTED);
147         }
148         $this->connection = $conn;
149
150         return DB_OK;
151     }
152
153     // }}}
154     // {{{ disconnect()
155
156     /**
157      * Log out and disconnect from the database.
158      *
159      * @access public
160      * @return bool true on success, false if not connected.
161      * @todo fix return values
162      */
163     function disconnect()
164     {
165         $ret = @sqlite_close($this->connection);
166         $this->connection = null;
167         return $ret;
168     }
169
170     // }}}
171     // {{{ simpleQuery()
172
173     /**
174      * Send a query to SQLite and returns the results as a SQLite resource
175      * identifier.
176      *
177      * @param the SQL query
178      * @access public
179      * @return mixed returns a valid SQLite result for successful SELECT
180      * queries, DB_OK for other successful queries. A DB error is
181      * returned on failure.
182      */
183     function simpleQuery($query)
184     {
185         $ismanip = DB::isManip($query);
186         $this->last_query = $query;
187         $query = $this->_modifyQuery($query);
188         @ini_set('track_errors', true);
189         $result = @sqlite_query($query, $this->connection);
190         ini_restore('track_errors');
191         $this->_lasterror = isset($php_errormsg) ? $php_errormsg : '';
192         $this->result = $result;
193         if (!$this->result) {
194             return $this->sqliteRaiseError(null);
195         }
196
197         /* sqlite_query() seems to allways return a resource */
198         /* so cant use that. Using $ismanip instead          */
199         if (!$ismanip) {
200             $numRows = $this->numRows($result);
201
202             /* if numRows() returned PEAR_Error */
203             if (is_object($numRows)) {
204                 return $numRows;
205             }
206             return $result;
207         }
208         return DB_OK;
209     }
210
211     // }}}
212     // {{{ nextResult()
213
214     /**
215      * Move the internal sqlite result pointer to the next available result.
216      *
217      * @param a valid sqlite result resource
218      * @access public
219      * @return true if a result is available otherwise return false
220      */
221     function nextResult($result)
222     {
223         return false;
224     }
225
226     // }}}
227     // {{{ fetchInto()
228
229     /**
230      * Fetch a row and insert the data into an existing array.
231      *
232      * Formating of the array and the data therein are configurable.
233      * See DB_result::fetchInto() for more information.
234      *
235      * @param resource $result    query result identifier
236      * @param array    $arr       (reference) array where data from the row
237      *                            should be placed
238      * @param int      $fetchmode how the resulting array should be indexed
239      * @param int      $rownum    the row number to fetch
240      *
241      * @return mixed DB_OK on success, null when end of result set is
242      *               reached or on failure
243      *
244      * @see DB_result::fetchInto()
245      * @access private
246      */
247     function fetchInto($result, &$arr, $fetchmode, $rownum=null)
248     {
249         if ($rownum !== null) {
250             if (!@sqlite_seek($this->result, $rownum)) {
251                 return null;
252             }
253         }
254         if ($fetchmode & DB_FETCHMODE_ASSOC) {
255             $arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
256             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
257                 $arr = array_change_key_case($arr, CASE_LOWER);
258             }
259         } else {
260             $arr = @sqlite_fetch_array($result, SQLITE_NUM);
261         }
262         if (!$arr) {
263             /* See: http://bugs.php.net/bug.php?id=22328 */
264             return null;
265         }
266         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
267             /*
268              * Even though this DBMS already trims output, we do this because
269              * a field might have intentional whitespace at the end that
270              * gets removed by DB_PORTABILITY_RTRIM under another driver.
271              */
272             $this->_rtrimArrayValues($arr);
273         }
274         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
275             $this->_convertNullArrayValuesToEmpty($arr);
276         }
277         return DB_OK;
278     }
279
280     // }}}
281     // {{{ freeResult()
282
283     /**
284      * Free the internal resources associated with $result.
285      *
286      * @param $result SQLite result identifier
287      * @access public
288      * @return bool true on success, false if $result is invalid
289      */
290     function freeResult(&$result)
291     {
292         // XXX No native free?
293         if (!is_resource($result)) {
294             return false;
295         }
296         $result = null;
297         return true;
298     }
299
300     // }}}
301     // {{{ numCols()
302
303     /**
304      * Gets the number of columns in a result set.
305      *
306      * @return number of columns in a result set
307      */
308     function numCols($result)
309     {
310         $cols = @sqlite_num_fields($result);
311         if (!$cols) {
312             return $this->sqliteRaiseError();
313         }
314         return $cols;
315     }
316
317     // }}}
318     // {{{ numRows()
319
320     /**
321      * Gets the number of rows affected by a query.
322      *
323      * @return number of rows affected by the last query
324      */
325     function numRows($result)
326     {
327         $rows = @sqlite_num_rows($result);
328         if (!is_integer($rows)) {
329             return $this->raiseError();
330         }
331         return $rows;
332     }
333
334     // }}}
335     // {{{ affected()
336
337     /**
338      * Gets the number of rows affected by a query.
339      *
340      * @return number of rows affected by the last query
341      */
342     function affectedRows()
343     {
344         return @sqlite_changes($this->connection);
345     }
346
347     // }}}
348     // {{{ errorNative()
349
350     /**
351      * Get the native error string of the last error (if any) that
352      * occured on the current connection.
353      *
354      * This is used to retrieve more meaningfull error messages DB_pgsql
355      * way since sqlite_last_error() does not provide adequate info.
356      *
357      * @return string native SQLite error message
358      */
359     function errorNative()
360     {
361         return($this->_lasterror);
362     }
363
364     // }}}
365     // {{{ errorCode()
366
367     /**
368      * Determine PEAR::DB error code from the database's text error message.
369      *
370      * @param  string  $errormsg  error message returned from the database
371      * @return integer  an error number from a DB error constant
372      */
373     function errorCode($errormsg)
374     {
375         static $error_regexps;
376         if (!isset($error_regexps)) {
377             $error_regexps = array(
378                 '/^no such table:/' => DB_ERROR_NOSUCHTABLE,
379                 '/^table .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
380                 '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
381                 '/is not unique/' => DB_ERROR_CONSTRAINT,
382                 '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
383                 '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
384                 '/^no such column:/' => DB_ERROR_NOSUCHFIELD,
385                 '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX
386             );
387         }
388         foreach ($error_regexps as $regexp => $code) {
389             if (preg_match($regexp, $errormsg)) {
390                 return $code;
391             }
392         }
393         // Fall back to DB_ERROR if there was no mapping.
394         return DB_ERROR;
395     }
396
397     // }}}
398     // {{{ dropSequence()
399
400     /**
401      * Deletes a sequence
402      *
403      * @param string $seq_name  name of the sequence to be deleted
404      *
405      * @return int  DB_OK on success.  DB_Error if problems.
406      *
407      * @internal
408      * @see DB_common::dropSequence()
409      * @access public
410      */
411     function dropSequence($seq_name)
412     {
413         $seqname = $this->getSequenceName($seq_name);
414         return $this->query("DROP TABLE $seqname");
415     }
416
417     /**
418      * Creates a new sequence
419      *
420      * @param string $seq_name  name of the new sequence
421      *
422      * @return int  DB_OK on success.  A DB_Error object is returned if
423      *              problems arise.
424      *
425      * @internal
426      * @see DB_common::createSequence()
427      * @access public
428      */
429     function createSequence($seq_name)
430     {
431         $seqname = $this->getSequenceName($seq_name);
432         $query   = 'CREATE TABLE ' . $seqname .
433                    ' (id INTEGER UNSIGNED PRIMARY KEY) ';
434         $result  = $this->query($query);
435         if (DB::isError($result)) {
436             return($result);
437         }
438         $query   = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
439                     BEGIN
440                         DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
441                     END ";
442         $result  = $this->query($query);
443         if (DB::isError($result)) {
444             return($result);
445         }
446     }
447
448     // }}}
449     // {{{ nextId()
450
451     /**
452      * Returns the next free id in a sequence
453      *
454      * @param string  $seq_name  name of the sequence
455      * @param boolean $ondemand  when true, the seqence is automatically
456      *                           created if it does not exist
457      *
458      * @return int  the next id number in the sequence.  DB_Error if problem.
459      *
460      * @internal
461      * @see DB_common::nextID()
462      * @access public
463      */
464     function nextId($seq_name, $ondemand = true)
465     {
466         $seqname = $this->getSequenceName($seq_name);
467
468         do {
469             $repeat = 0;
470             $this->pushErrorHandling(PEAR_ERROR_RETURN);
471             $result = $this->query("INSERT INTO $seqname VALUES (NULL)");
472             $this->popErrorHandling();
473             if ($result == DB_OK) {
474                 $id = @sqlite_last_insert_rowid($this->connection);
475                 if ($id != 0) {
476                     return $id;
477                 }
478             } elseif ($ondemand && DB::isError($result) &&
479                       $result->getCode() == DB_ERROR_NOSUCHTABLE)
480             {
481                 $result = $this->createSequence($seq_name);
482                 if (DB::isError($result)) {
483                     return $this->raiseError($result);
484                 } else {
485                     $repeat = 1;
486                 }
487             }
488         } while ($repeat);
489
490         return $this->raiseError($result);
491     }
492
493     // }}}
494     // {{{ getSpecialQuery()
495
496     /**
497      * Returns the query needed to get some backend info.
498      *
499      * Refer to the online manual at http://sqlite.org/sqlite.html.
500      *
501      * @param string $type What kind of info you want to retrieve
502      * @return string The SQL query string
503      */
504     function getSpecialQuery($type, $args=array())
505     {
506         if (!is_array($args))
507             return $this->raiseError('no key specified', null, null, null,
508                                      'Argument has to be an array.');
509         switch (strtolower($type)) {
510             case 'master':
511                 return 'SELECT * FROM sqlite_master;';
512             case 'tables':
513                 return "SELECT name FROM sqlite_master WHERE type='table' "
514                        . 'UNION ALL SELECT name FROM sqlite_temp_master '
515                        . "WHERE type='table' ORDER BY name;";
516             case 'schema':
517                 return 'SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL '
518                        . 'SELECT * FROM sqlite_temp_master) '
519                        . "WHERE type!='meta' ORDER BY tbl_name, type DESC, name;";
520             case 'schemax':
521             case 'schema_x':
522                 /*
523                  * Use like:
524                  * $res = $db->query($db->getSpecialQuery('schema_x', array('table' => 'table3')));
525                  */
526                 return 'SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL '
527                        . 'SELECT * FROM sqlite_temp_master) '
528                        . "WHERE tbl_name LIKE '{$args['table']}' AND type!='meta' "
529                        . 'ORDER BY type DESC, name;';
530             case 'alter':
531                 /*
532                  * SQLite does not support ALTER TABLE; this is a helper query
533                  * to handle this. 'table' represents the table name, 'rows'
534                  * the news rows to create, 'save' the row(s) to keep _with_
535                  * the data.
536                  *
537                  * Use like:
538                  * $args = array(
539                  *     'table' => $table,
540                  *     'rows'  => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
541                  *     'save'  => "NULL, titel, content, datetime"
542                  * );
543                  * $res = $db->query( $db->getSpecialQuery('alter', $args));
544                  */
545                 $rows = strtr($args['rows'], $this->keywords);
546
547                 $q = array(
548                     'BEGIN TRANSACTION',
549                     "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
550                     "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
551                     "DROP TABLE {$args['table']}",
552                     "CREATE TABLE {$args['table']} ({$args['rows']})",
553                     "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
554                     "DROP TABLE {$args['table']}_backup",
555                     'COMMIT',
556                 );
557
558                 // This is a dirty hack, since the above query will no get executed with a single
559                 // query call; so here the query method will be called directly and return a select instead.
560                 foreach ($q as $query) {
561                     $this->query($query);
562                 }
563                 return "SELECT * FROM {$args['table']};";
564             default:
565                 return null;
566         }
567     }
568
569     // }}}
570     // {{{ getDbFileStats()
571
572     /**
573      * Get the file stats for the current database.
574      *
575      * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
576      * atime, mtime, ctime, blksize, blocks or a numeric key between
577      * 0 and 12.
578      *
579      * @param string $arg Array key for stats()
580      * @return mixed array on an unspecified key, integer on a passed arg and
581      * false at a stats error.
582      */
583     function getDbFileStats($arg = '')
584     {
585         $stats = stat($this->dsn['database']);
586         if ($stats == false) {
587             return false;
588         }
589         if (is_array($stats)) {
590             if (is_numeric($arg)) {
591                 if (((int)$arg <= 12) & ((int)$arg >= 0)) {
592                     return false;
593                 }
594                 return $stats[$arg ];
595             }
596             if (array_key_exists(trim($arg), $stats)) {
597                 return $stats[$arg ];
598             }
599         }
600         return $stats;
601     }
602
603     // }}}
604     // {{{ escapeSimple()
605
606     /**
607      * Escape a string according to the current DBMS's standards
608      *
609      * In SQLite, this makes things safe for inserts/updates, but may
610      * cause problems when performing text comparisons against columns
611      * containing binary data. See the
612      * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
613      *
614      * @param string $str  the string to be escaped
615      *
616      * @return string  the escaped string
617      *
618      * @since 1.6.1
619      * @see DB_common::escapeSimple()
620      * @internal
621      */
622     function escapeSimple($str) {
623         return @sqlite_escape_string($str);
624     }
625
626     // }}}
627     // {{{ modifyLimitQuery()
628
629     function modifyLimitQuery($query, $from, $count)
630     {
631         $query = $query . " LIMIT $count OFFSET $from";
632         return $query;
633     }
634
635     // }}}
636     // {{{ modifyQuery()
637
638     /**
639      * "DELETE FROM table" gives 0 affected rows in SQLite.
640      *
641      * This little hack lets you know how many rows were deleted.
642      *
643      * @param string $query The SQL query string
644      * @return string The SQL query string
645      */
646     function _modifyQuery($query)
647     {
648         if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
649             if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
650                 $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
651                                       'DELETE FROM \1 WHERE 1=1', $query);
652             }
653         }
654         return $query;
655     }
656
657     // }}}
658     // {{{ sqliteRaiseError()
659
660     /**
661      * Gather information about an error, then use that info to create a
662      * DB error object and finally return that object.
663      *
664      * @param  integer  $errno  PEAR error number (usually a DB constant) if
665      *                          manually raising an error
666      * @return object  DB error object
667      * @see errorNative()
668      * @see errorCode()
669      * @see DB_common::raiseError()
670      */
671     function sqliteRaiseError($errno = null)
672     {
673
674         $native = $this->errorNative();
675         if ($errno === null) {
676             $errno = $this->errorCode($native);
677         }
678
679         $errorcode = @sqlite_last_error($this->connection);
680         $userinfo = "$errorcode ** $this->last_query";
681
682         return $this->raiseError($errno, null, null, $userinfo, $native);
683     }
684
685     // }}}
686 }
687
688 /*
689  * Local variables:
690  * tab-width: 4
691  * c-basic-offset: 4
692  * End:
693  */
694
695 ?>