MediaWiki  REL1_24
DatabaseSqlite.php
Go to the documentation of this file.
00001 <?php
00028 class DatabaseSqlite extends DatabaseBase {
00030     private static $fulltextEnabled = null;
00031 
00033     public $mDatabaseFile;
00034 
00036     protected $mAffectedRows;
00037 
00039     protected $mLastResult;
00040 
00042     protected $mConn;
00043 
00045     protected $lockMgr;
00046 
00047     function __construct( $p = null ) {
00048         global $wgSharedDB, $wgSQLiteDataDir;
00049 
00050         if ( !is_array( $p ) ) { // legacy calling pattern
00051             wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
00052             $args = func_get_args();
00053             $p = array(
00054                 'host' => isset( $args[0] ) ? $args[0] : false,
00055                 'user' => isset( $args[1] ) ? $args[1] : false,
00056                 'password' => isset( $args[2] ) ? $args[2] : false,
00057                 'dbname' => isset( $args[3] ) ? $args[3] : false,
00058                 'flags' => isset( $args[4] ) ? $args[4] : 0,
00059                 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global',
00060                 'schema' => 'get from global',
00061                 'foreign' => isset( $args[6] ) ? $args[6] : false
00062             );
00063         }
00064         $this->mDBname = $p['dbname'];
00065         parent::__construct( $p );
00066         // parent doesn't open when $user is false, but we can work with $dbName
00067         if ( $p['dbname'] && !$this->isOpen() ) {
00068             if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
00069                 if ( $wgSharedDB ) {
00070                     $this->attachDatabase( $wgSharedDB );
00071                 }
00072             }
00073         }
00074 
00075         $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "$wgSQLiteDataDir/locks" ) );
00076     }
00077 
00081     function getType() {
00082         return 'sqlite';
00083     }
00084 
00090     function implicitGroupby() {
00091         return false;
00092     }
00093 
00105     function open( $server, $user, $pass, $dbName ) {
00106         global $wgSQLiteDataDir;
00107 
00108         $this->close();
00109         $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
00110         if ( !is_readable( $fileName ) ) {
00111             $this->mConn = false;
00112             throw new DBConnectionError( $this, "SQLite database not accessible" );
00113         }
00114         $this->openFile( $fileName );
00115 
00116         return $this->mConn;
00117     }
00118 
00126     function openFile( $fileName ) {
00127         $err = false;
00128 
00129         $this->mDatabaseFile = $fileName;
00130         try {
00131             if ( $this->mFlags & DBO_PERSISTENT ) {
00132                 $this->mConn = new PDO( "sqlite:$fileName", '', '',
00133                     array( PDO::ATTR_PERSISTENT => true ) );
00134             } else {
00135                 $this->mConn = new PDO( "sqlite:$fileName", '', '' );
00136             }
00137         } catch ( PDOException $e ) {
00138             $err = $e->getMessage();
00139         }
00140 
00141         if ( !$this->mConn ) {
00142             wfDebug( "DB connection error: $err\n" );
00143             throw new DBConnectionError( $this, $err );
00144         }
00145 
00146         $this->mOpened = !!$this->mConn;
00147         # set error codes only, don't raise exceptions
00148         if ( $this->mOpened ) {
00149             $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
00150             # Enforce LIKE to be case sensitive, just like MySQL
00151             $this->query( 'PRAGMA case_sensitive_like = 1' );
00152 
00153             return $this->mConn;
00154         }
00155 
00156         return false;
00157     }
00158 
00163     protected function closeConnection() {
00164         $this->mConn = null;
00165 
00166         return true;
00167     }
00168 
00175     public static function generateFileName( $dir, $dbName ) {
00176         return "$dir/$dbName.sqlite";
00177     }
00178 
00183     function checkForEnabledSearch() {
00184         if ( self::$fulltextEnabled === null ) {
00185             self::$fulltextEnabled = false;
00186             $table = $this->tableName( 'searchindex' );
00187             $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
00188             if ( $res ) {
00189                 $row = $res->fetchRow();
00190                 self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
00191             }
00192         }
00193 
00194         return self::$fulltextEnabled;
00195     }
00196 
00201     static function getFulltextSearchModule() {
00202         static $cachedResult = null;
00203         if ( $cachedResult !== null ) {
00204             return $cachedResult;
00205         }
00206         $cachedResult = false;
00207         $table = 'dummy_search_test';
00208 
00209         $db = new DatabaseSqliteStandalone( ':memory:' );
00210 
00211         if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
00212             $cachedResult = 'FTS3';
00213         }
00214         $db->close();
00215 
00216         return $cachedResult;
00217     }
00218 
00230     function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
00231         global $wgSQLiteDataDir;
00232         if ( !$file ) {
00233             $file = self::generateFileName( $wgSQLiteDataDir, $name );
00234         }
00235         $file = $this->addQuotes( $file );
00236 
00237         return $this->query( "ATTACH DATABASE $file AS $name", $fname );
00238     }
00239 
00246     function isWriteQuery( $sql ) {
00247         return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql );
00248     }
00249 
00256     protected function doQuery( $sql ) {
00257         $res = $this->mConn->query( $sql );
00258         if ( $res === false ) {
00259             return false;
00260         } else {
00261             $r = $res instanceof ResultWrapper ? $res->result : $res;
00262             $this->mAffectedRows = $r->rowCount();
00263             $res = new ResultWrapper( $this, $r->fetchAll() );
00264         }
00265 
00266         return $res;
00267     }
00268 
00272     function freeResult( $res ) {
00273         if ( $res instanceof ResultWrapper ) {
00274             $res->result = null;
00275         } else {
00276             $res = null;
00277         }
00278     }
00279 
00284     function fetchObject( $res ) {
00285         if ( $res instanceof ResultWrapper ) {
00286             $r =& $res->result;
00287         } else {
00288             $r =& $res;
00289         }
00290 
00291         $cur = current( $r );
00292         if ( is_array( $cur ) ) {
00293             next( $r );
00294             $obj = new stdClass;
00295             foreach ( $cur as $k => $v ) {
00296                 if ( !is_numeric( $k ) ) {
00297                     $obj->$k = $v;
00298                 }
00299             }
00300 
00301             return $obj;
00302         }
00303 
00304         return false;
00305     }
00306 
00311     function fetchRow( $res ) {
00312         if ( $res instanceof ResultWrapper ) {
00313             $r =& $res->result;
00314         } else {
00315             $r =& $res;
00316         }
00317         $cur = current( $r );
00318         if ( is_array( $cur ) ) {
00319             next( $r );
00320 
00321             return $cur;
00322         }
00323 
00324         return false;
00325     }
00326 
00333     function numRows( $res ) {
00334         $r = $res instanceof ResultWrapper ? $res->result : $res;
00335 
00336         return count( $r );
00337     }
00338 
00343     function numFields( $res ) {
00344         $r = $res instanceof ResultWrapper ? $res->result : $res;
00345         if ( is_array( $r ) && count( $r ) > 0 ) {
00346             // The size of the result array is twice the number of fields. (Bug: 65578)
00347             return count( $r[0] ) / 2;
00348         } else {
00349             // If the result is empty return 0
00350             return 0;
00351         }
00352     }
00353 
00359     function fieldName( $res, $n ) {
00360         $r = $res instanceof ResultWrapper ? $res->result : $res;
00361         if ( is_array( $r ) ) {
00362             $keys = array_keys( $r[0] );
00363 
00364             return $keys[$n];
00365         }
00366 
00367         return false;
00368     }
00369 
00377     function tableName( $name, $format = 'quoted' ) {
00378         // table names starting with sqlite_ are reserved
00379         if ( strpos( $name, 'sqlite_' ) === 0 ) {
00380             return $name;
00381         }
00382 
00383         return str_replace( '"', '', parent::tableName( $name, $format ) );
00384     }
00385 
00392     function indexName( $index ) {
00393         return $index;
00394     }
00395 
00401     function insertId() {
00402         // PDO::lastInsertId yields a string :(
00403         return intval( $this->mConn->lastInsertId() );
00404     }
00405 
00410     function dataSeek( $res, $row ) {
00411         if ( $res instanceof ResultWrapper ) {
00412             $r =& $res->result;
00413         } else {
00414             $r =& $res;
00415         }
00416         reset( $r );
00417         if ( $row > 0 ) {
00418             for ( $i = 0; $i < $row; $i++ ) {
00419                 next( $r );
00420             }
00421         }
00422     }
00423 
00427     function lastError() {
00428         if ( !is_object( $this->mConn ) ) {
00429             return "Cannot return last error, no db connection";
00430         }
00431         $e = $this->mConn->errorInfo();
00432 
00433         return isset( $e[2] ) ? $e[2] : '';
00434     }
00435 
00439     function lastErrno() {
00440         if ( !is_object( $this->mConn ) ) {
00441             return "Cannot return last error, no db connection";
00442         } else {
00443             $info = $this->mConn->errorInfo();
00444 
00445             return $info[1];
00446         }
00447     }
00448 
00452     function affectedRows() {
00453         return $this->mAffectedRows;
00454     }
00455 
00466     function indexInfo( $table, $index, $fname = __METHOD__ ) {
00467         $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
00468         $res = $this->query( $sql, $fname );
00469         if ( !$res ) {
00470             return null;
00471         }
00472         if ( $res->numRows() == 0 ) {
00473             return false;
00474         }
00475         $info = array();
00476         foreach ( $res as $row ) {
00477             $info[] = $row->name;
00478         }
00479 
00480         return $info;
00481     }
00482 
00489     function indexUnique( $table, $index, $fname = __METHOD__ ) {
00490         $row = $this->selectRow( 'sqlite_master', '*',
00491             array(
00492                 'type' => 'index',
00493                 'name' => $this->indexName( $index ),
00494             ), $fname );
00495         if ( !$row || !isset( $row->sql ) ) {
00496             return null;
00497         }
00498 
00499         // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
00500         $indexPos = strpos( $row->sql, 'INDEX' );
00501         if ( $indexPos === false ) {
00502             return null;
00503         }
00504         $firstPart = substr( $row->sql, 0, $indexPos );
00505         $options = explode( ' ', $firstPart );
00506 
00507         return in_array( 'UNIQUE', $options );
00508     }
00509 
00516     function makeSelectOptions( $options ) {
00517         foreach ( $options as $k => $v ) {
00518             if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
00519                 $options[$k] = '';
00520             }
00521         }
00522 
00523         return parent::makeSelectOptions( $options );
00524     }
00525 
00530     protected function makeUpdateOptionsArray( $options ) {
00531         $options = parent::makeUpdateOptionsArray( $options );
00532         $options = self::fixIgnore( $options );
00533 
00534         return $options;
00535     }
00536 
00541     static function fixIgnore( $options ) {
00542         # SQLite uses OR IGNORE not just IGNORE
00543         foreach ( $options as $k => $v ) {
00544             if ( $v == 'IGNORE' ) {
00545                 $options[$k] = 'OR IGNORE';
00546             }
00547         }
00548 
00549         return $options;
00550     }
00551 
00556     function makeInsertOptions( $options ) {
00557         $options = self::fixIgnore( $options );
00558 
00559         return parent::makeInsertOptions( $options );
00560     }
00561 
00570     function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
00571         if ( !count( $a ) ) {
00572             return true;
00573         }
00574 
00575         # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
00576         if ( isset( $a[0] ) && is_array( $a[0] ) ) {
00577             $ret = true;
00578             foreach ( $a as $v ) {
00579                 if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
00580                     $ret = false;
00581                 }
00582             }
00583         } else {
00584             $ret = parent::insert( $table, $a, "$fname/single-row", $options );
00585         }
00586 
00587         return $ret;
00588     }
00589 
00597     function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
00598         if ( !count( $rows ) ) {
00599             return true;
00600         }
00601 
00602         # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
00603         if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
00604             $ret = true;
00605             foreach ( $rows as $v ) {
00606                 if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
00607                     $ret = false;
00608                 }
00609             }
00610         } else {
00611             $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
00612         }
00613 
00614         return $ret;
00615     }
00616 
00625     function textFieldSize( $table, $field ) {
00626         return -1;
00627     }
00628 
00632     function unionSupportsOrderAndLimit() {
00633         return false;
00634     }
00635 
00641     function unionQueries( $sqls, $all ) {
00642         $glue = $all ? ' UNION ALL ' : ' UNION ';
00643 
00644         return implode( $glue, $sqls );
00645     }
00646 
00650     function wasDeadlock() {
00651         return $this->lastErrno() == 5; // SQLITE_BUSY
00652     }
00653 
00657     function wasErrorReissuable() {
00658         return $this->lastErrno() == 17; // SQLITE_SCHEMA;
00659     }
00660 
00664     function wasReadOnlyError() {
00665         return $this->lastErrno() == 8; // SQLITE_READONLY;
00666     }
00667 
00671     public function getSoftwareLink() {
00672         return "[{{int:version-db-sqlite-url}} SQLite]";
00673     }
00674 
00678     function getServerVersion() {
00679         $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
00680 
00681         return $ver;
00682     }
00683 
00687     public function getServerInfo() {
00688         return wfMessage( self::getFulltextSearchModule()
00689             ? 'sqlite-has-fts'
00690             : 'sqlite-no-fts', $this->getServerVersion() )->text();
00691     }
00692 
00701     function fieldInfo( $table, $field ) {
00702         $tableName = $this->tableName( $table );
00703         $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
00704         $res = $this->query( $sql, __METHOD__ );
00705         foreach ( $res as $row ) {
00706             if ( $row->name == $field ) {
00707                 return new SQLiteField( $row, $tableName );
00708             }
00709         }
00710 
00711         return false;
00712     }
00713 
00714     protected function doBegin( $fname = '' ) {
00715         if ( $this->mTrxLevel == 1 ) {
00716             $this->commit( __METHOD__ );
00717         }
00718         try {
00719             $this->mConn->beginTransaction();
00720         } catch ( PDOException $e ) {
00721             throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
00722         }
00723         $this->mTrxLevel = 1;
00724     }
00725 
00726     protected function doCommit( $fname = '' ) {
00727         if ( $this->mTrxLevel == 0 ) {
00728             return;
00729         }
00730         try {
00731             $this->mConn->commit();
00732         } catch ( PDOException $e ) {
00733             throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
00734         }
00735         $this->mTrxLevel = 0;
00736     }
00737 
00738     protected function doRollback( $fname = '' ) {
00739         if ( $this->mTrxLevel == 0 ) {
00740             return;
00741         }
00742         $this->mConn->rollBack();
00743         $this->mTrxLevel = 0;
00744     }
00745 
00750     function strencode( $s ) {
00751         return substr( $this->addQuotes( $s ), 1, -1 );
00752     }
00753 
00758     function encodeBlob( $b ) {
00759         return new Blob( $b );
00760     }
00761 
00766     function decodeBlob( $b ) {
00767         if ( $b instanceof Blob ) {
00768             $b = $b->fetch();
00769         }
00770 
00771         return $b;
00772     }
00773 
00778     function addQuotes( $s ) {
00779         if ( $s instanceof Blob ) {
00780             return "x'" . bin2hex( $s->fetch() ) . "'";
00781         } elseif ( is_bool( $s ) ) {
00782             return (int)$s;
00783         } elseif ( strpos( $s, "\0" ) !== false ) {
00784             // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
00785             // This is a known limitation of SQLite's mprintf function which PDO should work around,
00786             // but doesn't. I have reported this to php.net as bug #63419:
00787             // https://bugs.php.net/bug.php?id=63419
00788             // There was already a similar report for SQLite3::escapeString, bug #62361:
00789             // https://bugs.php.net/bug.php?id=62361
00790             return "x'" . bin2hex( $s ) . "'";
00791         } else {
00792             return $this->mConn->quote( $s );
00793         }
00794     }
00795 
00799     function buildLike() {
00800         $params = func_get_args();
00801         if ( count( $params ) > 0 && is_array( $params[0] ) ) {
00802             $params = $params[0];
00803         }
00804 
00805         return parent::buildLike( $params ) . "ESCAPE '\' ";
00806     }
00807 
00811     public function getSearchEngine() {
00812         return "SearchSqlite";
00813     }
00814 
00820     public function deadlockLoop( /*...*/ ) {
00821         $args = func_get_args();
00822         $function = array_shift( $args );
00823 
00824         return call_user_func_array( $function, $args );
00825     }
00826 
00831     protected function replaceVars( $s ) {
00832         $s = parent::replaceVars( $s );
00833         if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
00834             // CREATE TABLE hacks to allow schema file sharing with MySQL
00835 
00836             // binary/varbinary column type -> blob
00837             $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
00838             // no such thing as unsigned
00839             $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
00840             // INT -> INTEGER
00841             $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
00842             // floating point types -> REAL
00843             $s = preg_replace(
00844                 '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
00845                 'REAL',
00846                 $s
00847             );
00848             // varchar -> TEXT
00849             $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
00850             // TEXT normalization
00851             $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
00852             // BLOB normalization
00853             $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
00854             // BOOL -> INTEGER
00855             $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
00856             // DATETIME -> TEXT
00857             $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
00858             // No ENUM type
00859             $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
00860             // binary collation type -> nothing
00861             $s = preg_replace( '/\bbinary\b/i', '', $s );
00862             // auto_increment -> autoincrement
00863             $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
00864             // No explicit options
00865             $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
00866             // AUTOINCREMENT should immedidately follow PRIMARY KEY
00867             $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
00868         } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
00869             // No truncated indexes
00870             $s = preg_replace( '/\(\d+\)/', '', $s );
00871             // No FULLTEXT
00872             $s = preg_replace( '/\bfulltext\b/i', '', $s );
00873         } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
00874             // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
00875             $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
00876         } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
00877             // INSERT IGNORE --> INSERT OR IGNORE
00878             $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
00879         }
00880 
00881         return $s;
00882     }
00883 
00884     public function lock( $lockName, $method, $timeout = 5 ) {
00885         global $wgSQLiteDataDir;
00886 
00887         if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed
00888             if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) {
00889                 throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." );
00890             }
00891         }
00892 
00893         return $this->lockMgr->lock( array( $lockName ), LockManager::LOCK_EX, $timeout )->isOK();
00894     }
00895 
00896     public function unlock( $lockName, $method ) {
00897         return $this->lockMgr->unlock( array( $lockName ), LockManager::LOCK_EX )->isOK();
00898     }
00899 
00906     function buildConcat( $stringList ) {
00907         return '(' . implode( ') || (', $stringList ) . ')';
00908     }
00909 
00910     public function buildGroupConcatField(
00911         $delim, $table, $field, $conds = '', $join_conds = array()
00912     ) {
00913         $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
00914 
00915         return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
00916     }
00917 
00926     function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
00927         $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
00928             $this->addQuotes( $oldName ) . " AND type='table'", $fname );
00929         $obj = $this->fetchObject( $res );
00930         if ( !$obj ) {
00931             throw new MWException( "Couldn't retrieve structure for table $oldName" );
00932         }
00933         $sql = $obj->sql;
00934         $sql = preg_replace(
00935             '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
00936             $this->addIdentifierQuotes( $newName ),
00937             $sql,
00938             1
00939         );
00940         if ( $temporary ) {
00941             if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
00942                 wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
00943             } else {
00944                 $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
00945             }
00946         }
00947 
00948         return $this->query( $sql, $fname );
00949     }
00950 
00959     function listTables( $prefix = null, $fname = __METHOD__ ) {
00960         $result = $this->select(
00961             'sqlite_master',
00962             'name',
00963             "type='table'"
00964         );
00965 
00966         $endArray = array();
00967 
00968         foreach ( $result as $table ) {
00969             $vars = get_object_vars( $table );
00970             $table = array_pop( $vars );
00971 
00972             if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
00973                 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
00974                     $endArray[] = $table;
00975                 }
00976             }
00977         }
00978 
00979         return $endArray;
00980     }
00981 } // end DatabaseSqlite class
00982 
00987 class DatabaseSqliteStandalone extends DatabaseSqlite {
00988     public function __construct( $fileName, $flags = 0 ) {
00989         $this->mFlags = $flags;
00990         $this->tablePrefix( null );
00991         $this->openFile( $fileName );
00992     }
00993 }
00994 
00998 class SQLiteField implements Field {
00999     private $info, $tableName;
01000 
01001     function __construct( $info, $tableName ) {
01002         $this->info = $info;
01003         $this->tableName = $tableName;
01004     }
01005 
01006     function name() {
01007         return $this->info->name;
01008     }
01009 
01010     function tableName() {
01011         return $this->tableName;
01012     }
01013 
01014     function defaultValue() {
01015         if ( is_string( $this->info->dflt_value ) ) {
01016             // Typically quoted
01017             if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
01018                 return str_replace( "''", "'", $this->info->dflt_value );
01019             }
01020         }
01021 
01022         return $this->info->dflt_value;
01023     }
01024 
01028     function isNullable() {
01029         return !$this->info->notnull;
01030     }
01031 
01032     function type() {
01033         return $this->info->type;
01034     }
01035 } // end SQLiteField