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