]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/sqlite.php
[ROUTES] Allow accept-header specification during router creation
[quix0rs-gnu-social.git] / extlib / DB / sqlite.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * The PEAR DB driver for PHP's sqlite extension
7  * for interacting with SQLite databases
8  *
9  * PHP version 5
10  *
11  * LICENSE: This source file is subject to version 3.0 of the PHP license
12  * that is available through the world-wide-web at the following URI:
13  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
14  * the PHP License and are unable to obtain it through the web, please
15  * send a note to license@php.net so we can mail you a copy immediately.
16  *
17  * @category   Database
18  * @package    DB
19  * @author     Urs Gehrig <urs@circle.ch>
20  * @author     Mika Tuupola <tuupola@appelsiini.net>
21  * @author     Daniel Convissor <danielc@php.net>
22  * @copyright  1997-2007 The PHP Group
23  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0 3.0
24  * @version    CVS: $Id$
25  * @link       http://pear.php.net/package/DB
26  */
27
28 /**
29  * Obtain the DB_common class so it can be extended from
30  */
31 //require_once 'DB/common.php';
32 require_once 'common.php';
33
34 /**
35  * The methods PEAR DB uses to interact with PHP's sqlite extension
36  * for interacting with SQLite databases
37  *
38  * These methods overload the ones declared in DB_common.
39  *
40  * NOTICE:  This driver needs PHP's track_errors ini setting to be on.
41  * It is automatically turned on when connecting to the database.
42  * Make sure your scripts don't turn it off.
43  *
44  * @category   Database
45  * @package    DB
46  * @author     Urs Gehrig <urs@circle.ch>
47  * @author     Mika Tuupola <tuupola@appelsiini.net>
48  * @author     Daniel Convissor <danielc@php.net>
49  * @copyright  1997-2007 The PHP Group
50  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0 3.0
51  * @version    Release: 1.9.2
52  * @link       http://pear.php.net/package/DB
53  */
54 class DB_sqlite extends DB_common
55 {
56     // {{{ properties
57
58     /**
59      * The DB driver type (mysql, oci8, odbc, etc.)
60      * @var string
61      */
62     public $phptype = 'sqlite';
63
64     /**
65      * The database syntax variant to be used (db2, access, etc.), if any
66      * @var string
67      */
68     public $dbsyntax = 'sqlite';
69
70     /**
71      * The capabilities of this DB implementation
72      *
73      * The 'new_link' element contains the PHP version that first provided
74      * new_link support for this DBMS.  Contains false if it's unsupported.
75      *
76      * Meaning of the 'limit' element:
77      *   + 'emulate' = emulate with fetch row by number
78      *   + 'alter'   = alter the query
79      *   + false     = skip rows
80      *
81      * @var array
82      */
83     public $features = array(
84         'limit' => 'alter',
85         'new_link' => false,
86         'numrows' => true,
87         'pconnect' => true,
88         'prepare' => false,
89         'ssl' => false,
90         'transactions' => false,
91     );
92
93     /**
94      * A mapping of native error codes to DB error codes
95      *
96      * {@internal  Error codes according to sqlite_exec.  See the online
97      * manual at http://sqlite.org/c_interface.html for info.
98      * This error handling based on sqlite_exec is not yet implemented.}}
99      *
100      * @var array
101      */
102     public $errorcode_map = array();
103
104     /**
105      * The raw database connection created by PHP
106      * @var resource
107      */
108     public $connection;
109
110     /**
111      * The DSN information for connecting to a database
112      * @var array
113      */
114     public $dsn = array();
115
116
117     /**
118      * SQLite data types
119      *
120      * @link http://www.sqlite.org/datatypes.html
121      *
122      * @var array
123      */
124     public $keywords = array(
125         'BLOB' => '',
126         'BOOLEAN' => '',
127         'CHARACTER' => '',
128         'CLOB' => '',
129         'FLOAT' => '',
130         'INTEGER' => '',
131         'KEY' => '',
132         'NATIONAL' => '',
133         'NUMERIC' => '',
134         'NVARCHAR' => '',
135         'PRIMARY' => '',
136         'TEXT' => '',
137         'TIMESTAMP' => '',
138         'UNIQUE' => '',
139         'VARCHAR' => '',
140         'VARYING' => '',
141     );
142
143     /**
144      * The most recent error message from $php_errormsg
145      * @var string
146      * @access private
147      */
148     public $_lasterror = '';
149
150
151     // }}}
152     // {{{ constructor
153
154     /**
155      * This constructor calls <kbd>parent::__construct()</kbd>
156      *
157      * @return void
158      */
159     public function __construct()
160     {
161         parent::__construct();
162     }
163
164     // }}}
165     // {{{ connect()
166
167     /**
168      * Connect to the database server, log in and open the database
169      *
170      * Don't call this method directly.  Use DB::connect() instead.
171      *
172      * PEAR DB's sqlite driver supports the following extra DSN options:
173      *   + mode  The permissions for the database file, in four digit
174      *            chmod octal format (eg "0600").
175      *
176      * Example of connecting to a database in read-only mode:
177      * <code>
178      * require_once 'DB.php';
179      *
180      * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400';
181      * $options = array(
182      *     'portability' => DB_PORTABILITY_ALL,
183      * );
184      *
185      * $db = DB::connect($dsn, $options);
186      * if ((new PEAR)->isError($db)) {
187      *     die($db->getMessage());
188      * }
189      * </code>
190      *
191      * @param array $dsn the data source name
192      * @param bool $persistent should the connection be persistent?
193      *
194      * @return int|object
195      */
196     public function connect($dsn, $persistent = false)
197     {
198         if (!PEAR::loadExtension('sqlite')) {
199             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
200         }
201
202         $this->dsn = $dsn;
203         if ($dsn['dbsyntax']) {
204             $this->dbsyntax = $dsn['dbsyntax'];
205         }
206
207         if (!$dsn['database']) {
208             return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
209         }
210
211         if ($dsn['database'] !== ':memory:') {
212             if (!file_exists($dsn['database'])) {
213                 if (!touch($dsn['database'])) {
214                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
215                 }
216                 if (!isset($dsn['mode']) ||
217                     !is_numeric($dsn['mode'])) {
218                     $mode = 0644;
219                 } else {
220                     $mode = octdec($dsn['mode']);
221                 }
222                 if (!chmod($dsn['database'], $mode)) {
223                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
224                 }
225                 if (!file_exists($dsn['database'])) {
226                     return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
227                 }
228             }
229             if (!is_file($dsn['database'])) {
230                 return $this->sqliteRaiseError(DB_ERROR_INVALID);
231             }
232             if (!is_readable($dsn['database'])) {
233                 return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
234             }
235         }
236
237         $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
238
239         // track_errors must remain on for simpleQuery()
240         @ini_set('track_errors', 1);
241         $php_errormsg = '';
242
243         if (!$this->connection = @$connect_function($dsn['database'])) {
244             return $this->raiseError(
245                 DB_ERROR_NODBSELECTED,
246                 null,
247                 null,
248                 null,
249                 $php_errormsg
250             );
251         }
252         return DB_OK;
253     }
254
255     // }}}
256     // {{{ disconnect()
257
258     /**
259      * Produces a DB_Error object regarding the current problem
260      *
261      * @param int $errno if the error is being manually raised pass a
262      *                     DB_ERROR* constant here.  If this isn't passed
263      *                     the error information gathered from the DBMS.
264      *
265      * @return object  the DB_Error object
266      *
267      * @see DB_common::raiseError(),
268      *      DB_sqlite::errorNative(), DB_sqlite::errorCode()
269      */
270     public function sqliteRaiseError($errno = null)
271     {
272         $native = $this->errorNative();
273         if ($errno === null) {
274             $errno = $this->errorCode($native);
275         }
276
277         $errorcode = @sqlite_last_error($this->connection);
278         $userinfo = "$errorcode ** $this->last_query";
279
280         return $this->raiseError($errno, null, null, $userinfo, $native);
281     }
282
283     // }}}
284     // {{{ simpleQuery()
285
286     /**
287      * Gets the DBMS' native error message produced by the last query
288      *
289      * {@internal This is used to retrieve more meaningfull error messages
290      * because sqlite_last_error() does not provide adequate info.}}
291      *
292      * @return string  the DBMS' error message
293      */
294     public function errorNative()
295     {
296         return $this->_lasterror;
297     }
298
299     // }}}
300     // {{{ nextResult()
301
302     /**
303      * Determines PEAR::DB error code from the database's text error message
304      *
305      * @param string $errormsg the error message returned from the database
306      *
307      * @return integer  the DB error number
308      */
309     public function errorCode($errormsg)
310     {
311         static $error_regexps;
312
313         // PHP 5.2+ prepends the function name to $php_errormsg, so we need
314         // this hack to work around it, per bug #9599.
315         $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg);
316
317         if (!isset($error_regexps)) {
318             $error_regexps = array(
319                 '/^no such table:/' => DB_ERROR_NOSUCHTABLE,
320                 '/^no such index:/' => DB_ERROR_NOT_FOUND,
321                 '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
322                 '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
323                 '/is not unique/' => DB_ERROR_CONSTRAINT,
324                 '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT,
325                 '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
326                 '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
327                 '/^no such column:/' => DB_ERROR_NOSUCHFIELD,
328                 '/no column named/' => DB_ERROR_NOSUCHFIELD,
329                 '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD,
330                 '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX,
331                 '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW,
332             );
333         }
334         foreach ($error_regexps as $regexp => $code) {
335             if (preg_match($regexp, $errormsg)) {
336                 return $code;
337             }
338         }
339         // Fall back to DB_ERROR if there was no mapping.
340         return DB_ERROR;
341     }
342
343     // }}}
344     // {{{ fetchInto()
345
346     /**
347      * Disconnects from the database server
348      *
349      * @return bool|void
350      */
351     public function disconnect()
352     {
353         $ret = @sqlite_close($this->connection);
354         $this->connection = null;
355         return $ret;
356     }
357
358     // }}}
359     // {{{ freeResult()
360
361     /**
362      * Sends a query to the database server
363      *
364      * NOTICE:  This method needs PHP's track_errors ini setting to be on.
365      * It is automatically turned on when connecting to the database.
366      * Make sure your scripts don't turn it off.
367      *
368      * @param string  the SQL query string
369      *
370      * @return mixed  + a PHP result resrouce for successful SELECT queries
371      *                + the DB_OK constant for other successful queries
372      *                + a DB_Error object on failure
373      */
374     public function simpleQuery($query)
375     {
376         $ismanip = $this->_checkManip($query);
377         $this->last_query = $query;
378         $query = $this->modifyQuery($query);
379
380         $php_errormsg = '';
381
382         $result = @sqlite_query($query, $this->connection);
383         $this->_lasterror = $php_errormsg ? $php_errormsg : '';
384
385         $this->result = $result;
386         if (!$this->result) {
387             return $this->sqliteRaiseError(null);
388         }
389
390         // sqlite_query() seems to allways return a resource
391         // so cant use that. Using $ismanip instead
392         if (!$ismanip) {
393             $numRows = $this->numRows($result);
394             if (is_object($numRows)) {
395                 // we've got PEAR_Error
396                 return $numRows;
397             }
398             return $result;
399         }
400         return DB_OK;
401     }
402
403     // }}}
404     // {{{ numCols()
405
406     /**
407      * Changes a query string for various DBMS specific reasons
408      *
409      * This little hack lets you know how many rows were deleted
410      * when running a "DELETE FROM table" query.  Only implemented
411      * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
412      *
413      * @param string $query the query string to modify
414      *
415      * @return string  the modified query string
416      *
417      * @access protected
418      * @see DB_common::setOption()
419      */
420     public function modifyQuery($query)
421     {
422         if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
423             if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
424                 $query = preg_replace(
425                     '/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
426                     'DELETE FROM \1 WHERE 1=1',
427                     $query
428                 );
429             }
430         }
431         return $query;
432     }
433
434     // }}}
435     // {{{ numRows()
436
437     /**
438      * Gets the number of rows in a result set
439      *
440      * This method is not meant to be called directly.  Use
441      * DB_result::numRows() instead.  It can't be declared "protected"
442      * because DB_result is a separate object.
443      *
444      * @param resource $result PHP's query result resource
445      *
446      * @return int|object
447      *
448      * @see DB_result::numRows()
449      */
450     public function numRows($result)
451     {
452         $rows = @sqlite_num_rows($result);
453         if ($rows === null) {
454             return $this->sqliteRaiseError();
455         }
456         return $rows;
457     }
458
459     // }}}
460     // {{{ affected()
461
462     /**
463      * Move the internal sqlite result pointer to the next available result
464      *
465      * @param resource $result the valid sqlite result resource
466      *
467      * @return bool  true if a result is available otherwise return false
468      */
469     public function nextResult($result)
470     {
471         return false;
472     }
473
474     // }}}
475     // {{{ dropSequence()
476
477     /**
478      * Places a row from the result set into the given array
479      *
480      * Formating of the array and the data therein are configurable.
481      * See DB_result::fetchInto() for more information.
482      *
483      * This method is not meant to be called directly.  Use
484      * DB_result::fetchInto() instead.  It can't be declared "protected"
485      * because DB_result is a separate object.
486      *
487      * @param resource $result the query result resource
488      * @param array $arr the referenced array to put the data in
489      * @param int $fetchmode how the resulting array should be indexed
490      * @param int $rownum the row number to fetch (0 = first row)
491      *
492      * @return mixed  DB_OK on success, NULL when the end of a result set is
493      *                 reached or on failure
494      *
495      * @see DB_result::fetchInto()
496      */
497     public function fetchInto($result, &$arr, $fetchmode, $rownum = null)
498     {
499         if ($rownum !== null) {
500             if (!@sqlite_seek($this->result, $rownum)) {
501                 return null;
502             }
503         }
504         if ($fetchmode & DB_FETCHMODE_ASSOC) {
505             $arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
506             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
507                 $arr = array_change_key_case($arr, CASE_LOWER);
508             }
509
510             /* Remove extraneous " characters from the fields in the result.
511              * Fixes bug #11716. */
512             if (is_array($arr) && count($arr) > 0) {
513                 $strippedArr = array();
514                 foreach ($arr as $field => $value) {
515                     $strippedArr[trim($field, '"')] = $value;
516                 }
517                 $arr = $strippedArr;
518             }
519         } else {
520             $arr = @sqlite_fetch_array($result, SQLITE_NUM);
521         }
522         if (!$arr) {
523             return null;
524         }
525         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
526             /*
527              * Even though this DBMS already trims output, we do this because
528              * a field might have intentional whitespace at the end that
529              * gets removed by DB_PORTABILITY_RTRIM under another driver.
530              */
531             $this->_rtrimArrayValues($arr);
532         }
533         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
534             $this->_convertNullArrayValuesToEmpty($arr);
535         }
536         return DB_OK;
537     }
538
539     /**
540      * Deletes the result set and frees the memory occupied by the result set
541      *
542      * This method is not meant to be called directly.  Use
543      * DB_result::free() instead.  It can't be declared "protected"
544      * because DB_result is a separate object.
545      *
546      * @param resource $result PHP's query result resource
547      *
548      * @return bool  TRUE on success, FALSE if $result is invalid
549      *
550      * @see DB_result::free()
551      */
552     public function freeResult(&$result)
553     {
554         // XXX No native free?
555         if (!is_resource($result)) {
556             return false;
557         }
558         $result = null;
559         return true;
560     }
561
562     // }}}
563     // {{{ nextId()
564
565     /**
566      * Gets the number of columns in a result set
567      *
568      * This method is not meant to be called directly.  Use
569      * DB_result::numCols() instead.  It can't be declared "protected"
570      * because DB_result is a separate object.
571      *
572      * @param resource $result PHP's query result resource
573      *
574      * @return int|object
575      *
576      * @see DB_result::numCols()
577      */
578     public function numCols($result)
579     {
580         $cols = @sqlite_num_fields($result);
581         if (!$cols) {
582             return $this->sqliteRaiseError();
583         }
584         return $cols;
585     }
586
587     // }}}
588     // {{{ getDbFileStats()
589
590     /**
591      * Determines the number of rows affected by a data maniuplation query
592      *
593      * 0 is returned for queries that don't manipulate data.
594      *
595      * @return int  the number of rows.  A DB_Error object on failure.
596      */
597     public function affectedRows()
598     {
599         return @sqlite_changes($this->connection);
600     }
601
602     // }}}
603     // {{{ escapeSimple()
604
605     /**
606      * Deletes a sequence
607      *
608      * @param string $seq_name name of the sequence to be deleted
609      *
610      * @return int  DB_OK on success.  A DB_Error object on failure.
611      *
612      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
613      *      DB_sqlite::nextID(), DB_sqlite::createSequence()
614      */
615     public function dropSequence($seq_name)
616     {
617         return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
618     }
619
620     // }}}
621     // {{{ modifyLimitQuery()
622
623     /**
624      * Returns the next free id in a sequence
625      *
626      * @param string $seq_name name of the sequence
627      * @param boolean $ondemand when true, the seqence is automatically
628      *                            created if it does not exist
629      *
630      * @return int|object
631      *               A DB_Error object on failure.
632      *
633      * @see DB_common::nextID(), DB_common::getSequenceName(),
634      *      DB_sqlite::createSequence(), DB_sqlite::dropSequence()
635      */
636     public function nextId($seq_name, $ondemand = true)
637     {
638         $seqname = $this->getSequenceName($seq_name);
639
640         do {
641             $repeat = 0;
642             $this->pushErrorHandling(PEAR_ERROR_RETURN);
643             $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
644             $this->popErrorHandling();
645             if ($result === DB_OK) {
646                 $id = @sqlite_last_insert_rowid($this->connection);
647                 if ($id != 0) {
648                     return $id;
649                 }
650             } elseif ($ondemand && DB::isError($result) &&
651                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
652                 $result = $this->createSequence($seq_name);
653                 if (DB::isError($result)) {
654                     return $this->raiseError($result);
655                 } else {
656                     $repeat = 1;
657                 }
658             }
659         } while ($repeat);
660
661         return $this->raiseError($result);
662     }
663
664     // }}}
665     // {{{ modifyQuery()
666
667     /**
668      * Creates a new sequence
669      *
670      * @param string $seq_name name of the new sequence
671      *
672      * @return int  DB_OK on success.  A DB_Error object on failure.
673      *
674      * @see DB_common::createSequence(), DB_common::getSequenceName(),
675      *      DB_sqlite::nextID(), DB_sqlite::dropSequence()
676      */
677     public function createSequence($seq_name)
678     {
679         $seqname = $this->getSequenceName($seq_name);
680         $query = 'CREATE TABLE ' . $seqname .
681             ' (id INTEGER UNSIGNED PRIMARY KEY) ';
682         $result = $this->query($query);
683         if (DB::isError($result)) {
684             return ($result);
685         }
686         $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
687                     BEGIN
688                         DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
689                     END ";
690         $result = $this->query($query);
691         //if (DB::isError($result)) {
692             return ($result);
693         //}
694     }
695
696     // }}}
697     // {{{ sqliteRaiseError()
698
699     /**
700      * Get the file stats for the current database
701      *
702      * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
703      * atime, mtime, ctime, blksize, blocks or a numeric key between
704      * 0 and 12.
705      *
706      * @param string $arg the array key for stats()
707      *
708      * @return mixed  an array on an unspecified key, integer on a passed
709      *                arg and false at a stats error
710      */
711     public function getDbFileStats($arg = '')
712     {
713         $stats = stat($this->dsn['database']);
714         if ($stats == false) {
715             return false;
716         }
717         if (is_array($stats)) {
718             if (is_numeric($arg)) {
719                 if (((int)$arg <= 12) & ((int)$arg >= 0)) {
720                     return false;
721                 }
722                 return $stats[$arg];
723             }
724             if (array_key_exists(trim($arg), $stats)) {
725                 return $stats[$arg];
726             }
727         }
728         return $stats;
729     }
730
731     // }}}
732     // {{{ errorNative()
733
734     /**
735      * Escapes a string according to the current DBMS's standards
736      *
737      * In SQLite, this makes things safe for inserts/updates, but may
738      * cause problems when performing text comparisons against columns
739      * containing binary data. See the
740      * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
741      *
742      * @param string $str the string to be escaped
743      *
744      * @return string  the escaped string
745      *
746      * @since Method available since Release 1.6.1
747      * @see DB_common::escapeSimple()
748      */
749     public function escapeSimple($str)
750     {
751         return @sqlite_escape_string($str);
752     }
753
754     // }}}
755     // {{{ errorCode()
756
757     /**
758      * Adds LIMIT clauses to a query string according to current DBMS standards
759      *
760      * @param string $query the query to modify
761      * @param int $from the row to start to fetching (0 = the first row)
762      * @param int $count the numbers of rows to fetch
763      * @param mixed $params array, string or numeric data to be used in
764      *                         execution of the statement.  Quantity of items
765      *                         passed must match quantity of placeholders in
766      *                         query:  meaning 1 placeholder for non-array
767      *                         parameters or 1 placeholder per array element.
768      *
769      * @return string  the query string with LIMIT clauses added
770      *
771      * @access protected
772      */
773     public function modifyLimitQuery($query, $from, $count, $params = array())
774     {
775         return "$query LIMIT $count OFFSET $from";
776     }
777
778     // }}}
779     // {{{ tableInfo()
780
781     /**
782      * Returns information about a table
783      *
784      * @param string $result a string containing the name of a table
785      * @param int $mode a valid tableInfo mode
786      *
787      * @return array|object
788      *                 A DB_Error object on failure.
789      *
790      * @see DB_common::tableInfo()
791      * @since Method available since Release 1.7.0
792      */
793     public function tableInfo($result, $mode = null)
794     {
795         if (is_string($result)) {
796             /*
797              * Probably received a table name.
798              * Create a result resource identifier.
799              */
800             $id = @sqlite_array_query(
801                 $this->connection,
802                 "PRAGMA table_info('$result');",
803                 SQLITE_ASSOC
804             );
805             $got_string = true;
806         } else {
807             $this->last_query = '';
808             return $this->raiseError(
809                 DB_ERROR_NOT_CAPABLE,
810                 null,
811                 null,
812                 null,
813                 'This DBMS can not obtain tableInfo' .
814                 ' from result sets'
815             );
816         }
817
818         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
819             $case_func = 'strtolower';
820         } else {
821             $case_func = 'strval';
822         }
823
824         $count = count($id);
825         $res = array();
826
827         if ($mode) {
828             $res['num_fields'] = $count;
829         }
830
831         for ($i = 0; $i < $count; $i++) {
832             if (strpos($id[$i]['type'], '(') !== false) {
833                 $bits = explode('(', $id[$i]['type']);
834                 $type = $bits[0];
835                 $len = rtrim($bits[1], ')');
836             } else {
837                 $type = $id[$i]['type'];
838                 $len = 0;
839             }
840
841             $flags = '';
842             if ($id[$i]['pk']) {
843                 $flags .= 'primary_key ';
844                 if (strtoupper($type) == 'INTEGER') {
845                     $flags .= 'auto_increment ';
846                 }
847             }
848             if ($id[$i]['notnull']) {
849                 $flags .= 'not_null ';
850             }
851             if ($id[$i]['dflt_value'] !== null) {
852                 $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']);
853             }
854             $flags = trim($flags);
855
856             $res[$i] = array(
857                 'table' => $case_func($result),
858                 'name' => $case_func($id[$i]['name']),
859                 'type' => $type,
860                 'len' => $len,
861                 'flags' => $flags,
862             );
863
864             if ($mode & DB_TABLEINFO_ORDER) {
865                 $res['order'][$res[$i]['name']] = $i;
866             }
867             if ($mode & DB_TABLEINFO_ORDERTABLE) {
868                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
869             }
870         }
871
872         return $res;
873     }
874
875     // }}}
876     // {{{ getSpecialQuery()
877
878     /**
879      * Obtains the query string needed for listing a given type of objects
880      *
881      * @param string $type the kind of objects you want to retrieve
882      * @param array $args SQLITE DRIVER ONLY: a private array of arguments
883      *                       used by the getSpecialQuery().  Do not use
884      *                       this directly.
885      *
886      * @return string  the SQL query string or null if the driver doesn't
887      *                  support the object type requested
888      *
889      * @access protected
890      * @see DB_common::getListOf()
891      */
892     public function getSpecialQuery($type, $args = array())
893     {
894         if (!is_array($args)) {
895             return $this->raiseError(
896                 'no key specified',
897                 null,
898                 null,
899                 null,
900                 'Argument has to be an array.'
901             );
902         }
903
904         switch ($type) {
905             case 'master':
906                 return 'SELECT * FROM sqlite_master;';
907             case 'tables':
908                 return "SELECT name FROM sqlite_master WHERE type='table' "
909                     . 'UNION ALL SELECT name FROM sqlite_temp_master '
910                     . "WHERE type='table' ORDER BY name;";
911             case 'schema':
912                 return 'SELECT sql FROM (SELECT * FROM sqlite_master '
913                     . 'UNION ALL SELECT * FROM sqlite_temp_master) '
914                     . "WHERE type!='meta' "
915                     . 'ORDER BY tbl_name, type DESC, name;';
916             case 'schemax':
917             case 'schema_x':
918                 /*
919                  * Use like:
920                  * $res = $db->query($db->getSpecialQuery('schema_x',
921                  *                   array('table' => 'table3')));
922                  */
923                 return 'SELECT sql FROM (SELECT * FROM sqlite_master '
924                     . 'UNION ALL SELECT * FROM sqlite_temp_master) '
925                     . "WHERE tbl_name LIKE '{$args['table']}' "
926                     . "AND type!='meta' "
927                     . 'ORDER BY type DESC, name;';
928             case 'alter':
929                 /*
930                  * SQLite does not support ALTER TABLE; this is a helper query
931                  * to handle this. 'table' represents the table name, 'rows'
932                  * the news rows to create, 'save' the row(s) to keep _with_
933                  * the data.
934                  *
935                  * Use like:
936                  * $args = array(
937                  *     'table' => $table,
938                  *     'rows'  => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
939                  *     'save'  => "NULL, titel, content, datetime"
940                  * );
941                  * $res = $db->query( $db->getSpecialQuery('alter', $args));
942                  */
943                 $rows = strtr($args['rows'], $this->keywords);
944
945                 $q = array(
946                     'BEGIN TRANSACTION',
947                     "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
948                     "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
949                     "DROP TABLE {$args['table']}",
950                     "CREATE TABLE {$args['table']} ({$args['rows']})",
951                     "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
952                     "DROP TABLE {$args['table']}_backup",
953                     'COMMIT',
954                 );
955
956                 /*
957                  * This is a dirty hack, since the above query will not get
958                  * executed with a single query call so here the query method
959                  * will be called directly and return a select instead.
960                  */
961                 foreach ($q as $query) {
962                     $this->query($query);
963                 }
964                 return "SELECT * FROM {$args['table']};";
965             default:
966                 return null;
967         }
968     }
969
970     // }}}
971 }
972
973 /*
974  * Local variables:
975  * tab-width: 4
976  * c-basic-offset: 4
977  * End:
978  */