thomascube
2006-02-22 745b1466fc76d5ded589e2469328086002430c1c
commit | author | age
c9462d 1 <?php
S 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
7 // | Stig. S. Bakken, Lukas Smith                                         |
8 // | All rights reserved.                                                 |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
11 // | API as well as database abstraction for PHP applications.            |
12 // | This LICENSE is in the BSD license style.                            |
13 // |                                                                      |
14 // | Redistribution and use in source and binary forms, with or without   |
15 // | modification, are permitted provided that the following conditions   |
16 // | are met:                                                             |
17 // |                                                                      |
18 // | Redistributions of source code must retain the above copyright       |
19 // | notice, this list of conditions and the following disclaimer.        |
20 // |                                                                      |
21 // | Redistributions in binary form must reproduce the above copyright    |
22 // | notice, this list of conditions and the following disclaimer in the  |
23 // | documentation and/or other materials provided with the distribution. |
24 // |                                                                      |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission.                                                  |
29 // |                                                                      |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41 // | POSSIBILITY OF SUCH DAMAGE.                                          |
42 // +----------------------------------------------------------------------+
43 // | Author: Lukas Smith <smith@backendmedia.com>                         |
44 // +----------------------------------------------------------------------+
45 //
46 // $Id$
47 //
48
49 /**
50  * MDB2 SQLite driver
51  *
52  * @package MDB2
53  * @category Database
54  * @author  Lukas Smith <smith@backendmedia.com>
55  */
56 class MDB2_Driver_sqlite extends MDB2_Driver_Common
57 {
58     // {{{ properties
59     var $escape_quotes = "'";
60
61     var $_lasterror = '';
62
63     // }}}
64     // {{{ constructor
65
66     /**
67     * Constructor
68     */
69     function __construct()
70     {
71         parent::__construct();
72
73         $this->phptype = 'sqlite';
74         $this->dbsyntax = 'sqlite';
75
76         $this->supported['sequences'] = true;
77         $this->supported['indexes'] = true;
78         $this->supported['affected_rows'] = true;
79         $this->supported['summary_functions'] = true;
80         $this->supported['order_by_text'] = true;
81         $this->supported['current_id'] = true;
82         $this->supported['limit_queries'] = true;
83         $this->supported['LOBs'] = true;
84         $this->supported['replace'] = true;
85         $this->supported['transactions'] = true;
86         $this->supported['sub_selects'] = true;
87         $this->supported['auto_increment'] = true;
88
89         $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off';
90         $this->options['fixed_float'] = 0;
91         $this->options['database_path'] = '';
92         $this->options['database_extension'] = '';
93     }
94
95     // }}}
96     // {{{ errorInfo()
97
98     /**
99      * This method is used to collect information about an error
100      *
101      * @param integer $error
102      * @return array
103      * @access public
104      */
105     function errorInfo($error = null)
106     {
107         $native_code = null;
108         if ($this->connection) {
109             $native_code = @sqlite_last_error($this->connection);
110         }
111         $native_msg  = @sqlite_error_string($native_code);
112
113         if (is_null($error)) {
114             static $error_regexps;
115             if (empty($error_regexps)) {
116                 $error_regexps = array(
117                     '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE,
118                     '/^no such index:/' => MDB2_ERROR_NOT_FOUND,
119                     '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS,
120                     '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT,
121                     '/is not unique/' => MDB2_ERROR_CONSTRAINT,
122                     '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT,
123                     '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT,
124                     '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL,
125                     '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD,
126                     '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD,
127                     '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX,
128                     '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW,
129                  );
130             }
131             foreach ($error_regexps as $regexp => $code) {
132                 if (preg_match($regexp, $this->_lasterror)) {
133                     $error = $code;
134                     break;
135                 }
136             }
137         }
138         return array($error, $native_code, $native_msg);
139     }
140
141     // }}}
142     // {{{ escape()
143
144     /**
145      * Quotes a string so it can be safely used in a query. It will quote
146      * the text so it can safely be used within a query.
147      *
148      * @param string $text the input string to quote
149      * @return string quoted string
150      * @access public
151      */
152     function escape($text)
153     {
154         return @sqlite_escape_string($text);
155     }
156
157     // }}}
158     // {{{ beginTransaction()
159
160     /**
161      * Start a transaction.
162      *
163      * @return mixed MDB2_OK on success, a MDB2 error on failure
164      * @access public
165      */
166     function beginTransaction()
167     {
168         $this->debug('starting transaction', 'beginTransaction');
169         if ($this->in_transaction) {
170             return MDB2_OK;  //nothing to do
171         }
172         if (!$this->destructor_registered && $this->opened_persistent) {
173             $this->destructor_registered = true;
174             register_shutdown_function('MDB2_closeOpenTransactions');
175         }
176         $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name'];
177         $result = $this->_doQuery($query, true);
178         if (PEAR::isError($result)) {
179             return $result;
180         }
181         $this->in_transaction = true;
182         return MDB2_OK;
183     }
184
185     // }}}
186     // {{{ commit()
187
188     /**
189      * Commit the database changes done during a transaction that is in
190      * progress.
191      *
192      * @return mixed MDB2_OK on success, a MDB2 error on failure
193      * @access public
194      */
195     function commit()
196     {
197         $this->debug('commit transaction', 'commit');
198         if (!$this->in_transaction) {
199             return $this->raiseError(MDB2_ERROR, null, null,
200                 'commit: transaction changes are being auto committed');
201         }
202         $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name'];
203         $result = $this->_doQuery($query, true);
204         if (PEAR::isError($result)) {
205             return $result;
206         }
207         $this->in_transaction = false;
208         return MDB2_OK;
209     }
210
211     // }}}
212     // {{{ rollback()
213
214     /**
215      * Cancel any database changes done during a transaction that is in
216      * progress.
217      *
218      * @return mixed MDB2_OK on success, a MDB2 error on failure
219      * @access public
220      */
221     function rollback()
222     {
223         $this->debug('rolling back transaction', 'rollback');
224         if (!$this->in_transaction) {
225             return $this->raiseError(MDB2_ERROR, null, null,
226                 'rollback: transactions can not be rolled back when changes are auto committed');
227         }
228         $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name'];
229         $result = $this->_doQuery($query, true);
230         if (PEAR::isError($result)) {
231             return $result;
232         }
233         $this->in_transaction = false;
234         return MDB2_OK;
235     }
236
237     // }}}
238     // {{{ getDatabaseFile()
239
240     /**
241      * Builds the string with path+dbname+extension
242      *
243      * @return string full database path+file
244      * @access protected
245      */
246     function _getDatabaseFile($database_name)
247     {
248         if ($database_name == '') {
249             return $database_name;
250         }
251         return $this->options['database_path'].$database_name.$this->options['database_extension'];
252     }
253
254     // }}}
255     // {{{ connect()
256
257     /**
258      * Connect to the database
259      *
260      * @return true on success, MDB2 Error Object on failure
261      **/
262     function connect()
263     {
264         $database_file = $this->_getDatabaseFile($this->database_name);
265         if (is_resource($this->connection)) {
266             if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
267                 && $this->connected_database_name == $database_file
268                 && $this->opened_persistent == $this->options['persistent']
269             ) {
270                 return MDB2_OK;
271             }
272             $this->disconnect(false);
273         }
274
275         if (!PEAR::loadExtension($this->phptype)) {
276             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
277                 'connect: extension '.$this->phptype.' is not compiled into PHP');
278         }
279
280         if (!empty($this->database_name)) {
281             if (!file_exists($database_file)) {
282                 if (!touch($database_file)) {
283                     return $this->raiseError(MDB2_ERROR_NOT_FOUND);
284                 }
285                 if (!isset($this->dsn['mode'])
286                     || !is_numeric($this->dsn['mode'])
287                 ) {
288                     $mode = 0644;
289                 } else {
290                     $mode = octdec($this->dsn['mode']);
291                 }
292                 if (!chmod($database_file, $mode)) {
293                     return $this->raiseError(MDB2_ERROR_NOT_FOUND);
294                 }
295                 if (!file_exists($database_file)) {
296                     return $this->raiseError(MDB2_ERROR_NOT_FOUND);
297                 }
298             }
299             if (!is_file($database_file)) {
300                 return $this->raiseError(MDB2_ERROR_INVALID);
301             }
302             if (!is_readable($database_file)) {
303                 return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION);
304             }
305
306             $connect_function = ($this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open');
307             $php_errormsg = '';
308             @ini_set('track_errors', true);
309             $connection = @$connect_function($database_file);
310             @ini_restore('track_errors');
311             $this->_lasterror = isset($php_errormsg) ? $php_errormsg : '';
312             if (!$connection) {
313                 return $this->raiseError(MDB2_ERROR_CONNECT_FAILED);
314             }
315             $this->connection = $connection;
316             $this->connected_dsn = $this->dsn;
317             $this->connected_database_name = $database_file;
318             $this->opened_persistent = $this->getoption('persistent');
319             $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
320         }
321         return MDB2_OK;
322     }
323
324     // }}}
325     // {{{ disconnect()
326
327     /**
328      * Log out and disconnect from the database.
329      *
330      * @return mixed true on success, false if not connected and error
331      *                object on error
332      * @access public
333      */
334     function disconnect($force = true)
335     {
336         if (is_resource($this->connection)) {
337             if (!$this->opened_persistent || $force) {
338                 @sqlite_close($this->connection);
339             }
340             $this->connection = 0;
341         }
342         return MDB2_OK;
343     }
344
345     // }}}
346     // {{{ _doQuery()
347
348     /**
349      * Execute a query
350      * @param string $query  query
351      * @param boolean $isManip  if the query is a manipulation query
352      * @param resource $connection
353      * @param string $database_name
354      * @return result or error object
355      * @access protected
356      */
357     function _doQuery($query, $isManip = false, $connection = null, $database_name = null)
358     {
359         $this->last_query = $query;
360         $this->debug($query, 'query');
361         if ($this->options['disable_query']) {
362             if ($isManip) {
363                 return MDB2_OK;
364             }
365             return null;
366         }
367
368         if (is_null($connection)) {
369             $error = $this->connect();
370             if (PEAR::isError($error)) {
371                 return $error;
372             }
373             $connection = $this->connection;
374         }
375
376         $function = $this->options['result_buffering']
377             ? 'sqlite_query' : 'sqlite_unbuffered_query';
378         $php_errormsg = '';
379         ini_set('track_errors', true);
380         $result = @$function($query.';', $connection);
381         ini_restore('track_errors');
382         $this->_lasterror = isset($php_errormsg) ? $php_errormsg : '';
383
384         if (!$result) {
385             return $this->raiseError();
386         }
387
388         if ($isManip) {
389             return @sqlite_changes($connection);
390         }
391         return $result;
392     }
393
394     // }}}
395     // {{{ _modifyQuery()
396
397     /**
398      * Changes a query string for various DBMS specific reasons
399      *
400      * @param string $query  query to modify
401      * @return the new (modified) query
402      * @access protected
403      */
404     function _modifyQuery($query, $isManip, $limit, $offset)
405     {
406         if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
407             if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
408                 $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
409                                       'DELETE FROM \1 WHERE 1=1', $query);
410             }
411         }
412         if ($limit > 0
413             && !preg_match('/LIMIT\s*\d(\s*(,|OFFSET)\s*\d+)?/i', $query)
414         ) {
415             $query = rtrim($query);
416             if (substr($query, -1) == ';') {
417                 $query = substr($query, 0, -1);
418             }
419             if ($isManip) {
420                 $query .= " LIMIT $limit";
421             } else {
422                 $query .= " LIMIT $offset,$limit";
423             }
424         }
425         return $query;
426     }
427
428
429     // }}}
430     // {{{ replace()
431
432     /**
433      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
434      * query, except that if there is already a row in the table with the same
435      * key field values, the REPLACE query just updates its values instead of
436      * inserting a new row.
437      *
438      * The REPLACE type of query does not make part of the SQL standards. Since
439      * practically only SQLite implements it natively, this type of query is
440      * emulated through this method for other DBMS using standard types of
441      * queries inside a transaction to assure the atomicity of the operation.
442      *
443      * @access public
444      *
445      * @param string $table name of the table on which the REPLACE query will
446      *  be executed.
447      * @param array $fields associative array that describes the fields and the
448      *  values that will be inserted or updated in the specified table. The
449      *  indexes of the array are the names of all the fields of the table. The
450      *  values of the array are also associative arrays that describe the
451      *  values and other properties of the table fields.
452      *
453      *  Here follows a list of field properties that need to be specified:
454      *
455      *    value:
456      *          Value to be assigned to the specified field. This value may be
457      *          of specified in database independent type format as this
458      *          function can perform the necessary datatype conversions.
459      *
460      *    Default:
461      *          this property is required unless the Null property
462      *          is set to 1.
463      *
464      *    type
465      *          Name of the type of the field. Currently, all types Metabase
466      *          are supported except for clob and blob.
467      *
468      *    Default: no type conversion
469      *
470      *    null
471      *          Boolean property that indicates that the value for this field
472      *          should be set to null.
473      *
474      *          The default value for fields missing in INSERT queries may be
475      *          specified the definition of a table. Often, the default value
476      *          is already null, but since the REPLACE may be emulated using
477      *          an UPDATE query, make sure that all fields of the table are
478      *          listed in this function argument array.
479      *
480      *    Default: 0
481      *
482      *    key
483      *          Boolean property that indicates that this field should be
484      *          handled as a primary key or at least as part of the compound
485      *          unique index of the table that will determine the row that will
486      *          updated if it exists or inserted a new row otherwise.
487      *
488      *          This function will fail if no key field is specified or if the
489      *          value of a key field is set to null because fields that are
490      *          part of unique index they may not be null.
491      *
492      *    Default: 0
493      *
494      * @return mixed MDB2_OK on success, a MDB2 error on failure
495      */
496     function replace($table, $fields)
497     {
498         $count = count($fields);
499         $query = $values = '';
500         $keys = $colnum = 0;
501         for (reset($fields); $colnum < $count; next($fields), $colnum++) {
502             $name = key($fields);
503             if ($colnum > 0) {
504                 $query .= ',';
505                 $values .= ',';
506             }
507             $query .= $name;
508             if (isset($fields[$name]['null']) && $fields[$name]['null']) {
509                 $value = 'NULL';
510             } else {
511                 $value = $this->quote($fields[$name]['value'], $fields[$name]['type']);
512             }
513             $values .= $value;
514             if (isset($fields[$name]['key']) && $fields[$name]['key']) {
515                 if ($value === 'NULL') {
516                     return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
517                         'replace: key value '.$name.' may not be NULL');
518                 }
519                 $keys++;
520             }
521         }
522         if ($keys == 0) {
523             return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
524                 'replace: not specified which fields are keys');
525         }
526         $query = "REPLACE INTO $table ($query) VALUES ($values)";
527         $this->last_query = $query;
528         $this->debug($query, 'query');
529         return $this->_doQuery($query, true);
530     }
531
532     // }}}
533     // {{{ nextID()
534
535     /**
536      * returns the next free id of a sequence
537      *
538      * @param string  $seq_name name of the sequence
539      * @param boolean $ondemand when true the seqence is
540      *                          automatic created, if it
541      *                          not exists
542      *
543      * @return mixed MDB2 Error Object or id
544      * @access public
545      */
546     function nextID($seq_name, $ondemand = true)
547     {
548         $sequence_name = $this->getSequenceName($seq_name);
549         $query = "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (NULL)";
550         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
551         $result = $this->_doQuery($query, true);
552         $this->popExpect();
553         if (PEAR::isError($result)) {
554             if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
555                 $this->loadModule('Manager');
556                 // Since we are creating the sequence on demand
557                 // we know the first id = 1 so initialize the
558                 // sequence at 2
559                 $result = $this->manager->createSequence($seq_name, 2);
560                 if (PEAR::isError($result)) {
561                     return $this->raiseError(MDB2_ERROR, null, null,
562                         'nextID: on demand sequence '.$seq_name.' could not be created');
563                 } else {
564                     // First ID of a newly created sequence is 1
565                     return 1;
566                 }
567             }
568             return $result;
569         }
570         $value = @sqlite_last_insert_rowid($this->connection);
571         if (is_numeric($value)
572             && PEAR::isError($this->_doQuery("DELETE FROM $sequence_name WHERE ".$this->options['seqcol_name']." < $value", true))
573         ) {
574             $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
575         }
576         return $value;
577     }
578
579     // }}}
580     // {{{ getAfterID()
581
582     /**
583      * returns the autoincrement ID if supported or $id
584      *
585      * @param mixed $id value as returned by getBeforeId()
586      * @param string $table name of the table into which a new row was inserted
587      * @return mixed MDB2 Error Object or id
588      * @access public
589      */
590     function getAfterID($id, $table)
591     {
592         $this->loadModule('Native');
593         return $this->native->getInsertID();
594     }
595
596     // }}}
597     // {{{ currID()
598
599     /**
600      * returns the current id of a sequence
601      *
602      * @param string  $seq_name name of the sequence
603      * @return mixed MDB2 Error Object or id
604      * @access public
605      */
606     function currID($seq_name)
607     {
608         $sequence_name = $this->getSequenceName($seq_name);
609         return $this->queryOne("SELECT MAX(".$this->options['seqcol_name'].") FROM $sequence_name", 'integer');
610     }
611 }
612
613 class MDB2_Result_sqlite extends MDB2_Result_Common
614 {
615     // }}}
616     // {{{ fetchRow()
617
618     /**
619      * Fetch a row and insert the data into an existing array.
620      *
621      * @param int       $fetchmode  how the array data should be indexed
622      * @param int    $rownum    number of the row where the data can be found
623      * @return int data array on success, a MDB2 error on failure
624      * @access public
625      */
626     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
627     {
628         if (!is_null($rownum)) {
629             $seek = $this->seek($rownum);
630             if (PEAR::isError($seek)) {
631                 return $seek;
632             }
633         }
634         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
635             $fetchmode = $this->db->fetchmode;
636         }
637         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
638             $row = @sqlite_fetch_array($this->result, SQLITE_ASSOC);
639             if (is_array($row)
640                 && $this->db->options['portability'] & MDB2_PORTABILITY_LOWERCASE
641             ) {
642                 $row = array_change_key_case($row, CASE_LOWER);
643             }
644         } else {
645            $row = @sqlite_fetch_array($this->result, SQLITE_NUM);
646         }
647         if (!$row) {
648             if (is_null($this->result)) {
649                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
650                     'fetchRow: resultset has already been freed');
651             }
652             return null;
653         }
654         if ($this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) {
655             $this->db->_convertEmptyArrayValuesToNull($row);
656         }
657         if (isset($this->values)) {
658             $this->_assignBindColumns($row);
659         }
660         if (isset($this->types)) {
661             $row = $this->db->datatype->convertResultRow($this->types, $row);
662         }
663         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
664             $object_class = $this->db->options['fetch_class'];
665             if ($object_class == 'stdClass') {
666                 $row = (object) $row;
667             } else {
668                 $row = &new $object_class($row);
669             }
670         }
671         ++$this->rownum;
672         return $row;
673     }
674
675     // }}}
676     // {{{ _getColumnNames()
677
678     /**
679      * Retrieve the names of columns returned by the DBMS in a query result.
680      *
681      * @return mixed                an associative array variable
682      *                              that will hold the names of columns. The
683      *                              indexes of the array are the column names
684      *                              mapped to lower case and the values are the
685      *                              respective numbers of the columns starting
686      *                              from 0. Some DBMS may not return any
687      *                              columns when the result set does not
688      *                              contain any rows.
689      *
690      *                              a MDB2 error on failure
691      * @access private
692      */
693     function _getColumnNames()
694     {
695         $columns = array();
696         $numcols = $this->numCols();
697         if (PEAR::isError($numcols)) {
698             return $numcols;
699         }
700         for ($column = 0; $column < $numcols; $column++) {
701             $column_name = @sqlite_field_name($this->result, $column);
702             $columns[$column_name] = $column;
703         }
704         if ($this->db->options['portability'] & MDB2_PORTABILITY_LOWERCASE) {
705             $columns = array_change_key_case($columns, CASE_LOWER);
706         }
707         return $columns;
708     }
709
710     // }}}
711     // {{{ numCols()
712
713     /**
714      * Count the number of columns returned by the DBMS in a query result.
715      *
716      * @access public
717      * @return mixed integer value with the number of columns, a MDB2 error
718      *                       on failure
719      */
720     function numCols()
721     {
722         $cols = @sqlite_num_fields($this->result);
723         if (is_null($cols)) {
724             if (is_null($this->result)) {
725                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
726                     'numCols: resultset has already been freed');
727             }
728             return $this->db->raiseError();
729         }
730         return $cols;
731     }
732 }
733
734 class MDB2_BufferedResult_sqlite extends MDB2_Result_sqlite
735 {
736     // {{{ seek()
737
738     /**
739     * seek to a specific row in a result set
740     *
741     * @param int    $rownum    number of the row where the data can be found
742     * @return mixed MDB2_OK on success, a MDB2 error on failure
743     * @access public
744     */
745     function seek($rownum = 0)
746     {
747         if (!@sqlite_seek($this->result, $rownum)) {
748             if (is_null($this->result)) {
749                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
750                     'seek: resultset has already been freed');
751             }
752             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
753                 'seek: tried to seek to an invalid row number ('.$rownum.')');
754         }
755         $this->rownum = $rownum - 1;
756         return MDB2_OK;
757     }
758
759     // }}}
760     // {{{ valid()
761
762     /**
763     * check if the end of the result set has been reached
764     *
765     * @return mixed true or false on sucess, a MDB2 error on failure
766     * @access public
767     */
768     function valid()
769     {
770         $numrows = $this->numRows();
771         if (PEAR::isError($numrows)) {
772             return $numrows;
773         }
774         return $this->rownum < ($numrows - 1);
775     }
776
777     // }}}
778     // {{{ numRows()
779
780     /**
781     * returns the number of rows in a result object
782     *
783     * @return mixed MDB2 Error Object or the number of rows
784     * @access public
785     */
786     function numRows()
787     {
788         $rows = @sqlite_num_rows($this->result);
789         if (is_null($rows)) {
790             if (is_null($this->result)) {
791                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
792                     'numRows: resultset has already been freed');
793             }
794             return $this->raiseError();
795         }
796         return $rows;
797     }
798 }
799
800 class MDB2_Statement_sqlite extends MDB2_Statement_Common
801 {
802
803 }
804
805 ?>