MediaWiki  REL1_23
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 
00346         return is_array( $r ) ? count( $r[0] ) : 0;
00347     }
00348 
00354     function fieldName( $res, $n ) {
00355         $r = $res instanceof ResultWrapper ? $res->result : $res;
00356         if ( is_array( $r ) ) {
00357             $keys = array_keys( $r[0] );
00358 
00359             return $keys[$n];
00360         }
00361 
00362         return false;
00363     }
00364 
00372     function tableName( $name, $format = 'quoted' ) {
00373         // table names starting with sqlite_ are reserved
00374         if ( strpos( $name, 'sqlite_' ) === 0 ) {
00375             return $name;
00376         }
00377 
00378         return str_replace( '"', '', parent::tableName( $name, $format ) );
00379     }
00380 
00387     function indexName( $index ) {
00388         return $index;
00389     }
00390 
00396     function insertId() {
00397         // PDO::lastInsertId yields a string :(
00398         return intval( $this->mConn->lastInsertId() );
00399     }
00400 
00405     function dataSeek( $res, $row ) {
00406         if ( $res instanceof ResultWrapper ) {
00407             $r =& $res->result;
00408         } else {
00409             $r =& $res;
00410         }
00411         reset( $r );
00412         if ( $row > 0 ) {
00413             for ( $i = 0; $i < $row; $i++ ) {
00414                 next( $r );
00415             }
00416         }
00417     }
00418 
00422     function lastError() {
00423         if ( !is_object( $this->mConn ) ) {
00424             return "Cannot return last error, no db connection";
00425         }
00426         $e = $this->mConn->errorInfo();
00427 
00428         return isset( $e[2] ) ? $e[2] : '';
00429     }
00430 
00434     function lastErrno() {
00435         if ( !is_object( $this->mConn ) ) {
00436             return "Cannot return last error, no db connection";
00437         } else {
00438             $info = $this->mConn->errorInfo();
00439 
00440             return $info[1];
00441         }
00442     }
00443 
00447     function affectedRows() {
00448         return $this->mAffectedRows;
00449     }
00450 
00461     function indexInfo( $table, $index, $fname = __METHOD__ ) {
00462         $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
00463         $res = $this->query( $sql, $fname );
00464         if ( !$res ) {
00465             return null;
00466         }
00467         if ( $res->numRows() == 0 ) {
00468             return false;
00469         }
00470         $info = array();
00471         foreach ( $res as $row ) {
00472             $info[] = $row->name;
00473         }
00474 
00475         return $info;
00476     }
00477 
00484     function indexUnique( $table, $index, $fname = __METHOD__ ) {
00485         $row = $this->selectRow( 'sqlite_master', '*',
00486             array(
00487                 'type' => 'index',
00488                 'name' => $this->indexName( $index ),
00489             ), $fname );
00490         if ( !$row || !isset( $row->sql ) ) {
00491             return null;
00492         }
00493 
00494         // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
00495         $indexPos = strpos( $row->sql, 'INDEX' );
00496         if ( $indexPos === false ) {
00497             return null;
00498         }
00499         $firstPart = substr( $row->sql, 0, $indexPos );
00500         $options = explode( ' ', $firstPart );
00501 
00502         return in_array( 'UNIQUE', $options );
00503     }
00504 
00511     function makeSelectOptions( $options ) {
00512         foreach ( $options as $k => $v ) {
00513             if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
00514                 $options[$k] = '';
00515             }
00516         }
00517 
00518         return parent::makeSelectOptions( $options );
00519     }
00520 
00525     protected function makeUpdateOptionsArray( $options ) {
00526         $options = parent::makeUpdateOptionsArray( $options );
00527         $options = self::fixIgnore( $options );
00528 
00529         return $options;
00530     }
00531 
00536     static function fixIgnore( $options ) {
00537         # SQLite uses OR IGNORE not just IGNORE
00538         foreach ( $options as $k => $v ) {
00539             if ( $v == 'IGNORE' ) {
00540                 $options[$k] = 'OR IGNORE';
00541             }
00542         }
00543 
00544         return $options;
00545     }
00546 
00551     function makeInsertOptions( $options ) {
00552         $options = self::fixIgnore( $options );
00553 
00554         return parent::makeInsertOptions( $options );
00555     }
00556 
00565     function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
00566         if ( !count( $a ) ) {
00567             return true;
00568         }
00569 
00570         # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
00571         if ( isset( $a[0] ) && is_array( $a[0] ) ) {
00572             $ret = true;
00573             foreach ( $a as $v ) {
00574                 if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
00575                     $ret = false;
00576                 }
00577             }
00578         } else {
00579             $ret = parent::insert( $table, $a, "$fname/single-row", $options );
00580         }
00581 
00582         return $ret;
00583     }
00584 
00592     function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
00593         if ( !count( $rows ) ) {
00594             return true;
00595         }
00596 
00597         # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
00598         if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
00599             $ret = true;
00600             foreach ( $rows as $v ) {
00601                 if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
00602                     $ret = false;
00603                 }
00604             }
00605         } else {
00606             $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
00607         }
00608 
00609         return $ret;
00610     }
00611 
00620     function textFieldSize( $table, $field ) {
00621         return -1;
00622     }
00623 
00627     function unionSupportsOrderAndLimit() {
00628         return false;
00629     }
00630 
00636     function unionQueries( $sqls, $all ) {
00637         $glue = $all ? ' UNION ALL ' : ' UNION ';
00638 
00639         return implode( $glue, $sqls );
00640     }
00641 
00645     function wasDeadlock() {
00646         return $this->lastErrno() == 5; // SQLITE_BUSY
00647     }
00648 
00652     function wasErrorReissuable() {
00653         return $this->lastErrno() == 17; // SQLITE_SCHEMA;
00654     }
00655 
00659     function wasReadOnlyError() {
00660         return $this->lastErrno() == 8; // SQLITE_READONLY;
00661     }
00662 
00666     public function getSoftwareLink() {
00667         return "[{{int:version-db-sqlite-url}} SQLite]";
00668     }
00669 
00673     function getServerVersion() {
00674         $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
00675 
00676         return $ver;
00677     }
00678 
00682     public function getServerInfo() {
00683         return wfMessage( self::getFulltextSearchModule()
00684             ? 'sqlite-has-fts'
00685             : 'sqlite-no-fts', $this->getServerVersion() )->text();
00686     }
00687 
00696     function fieldInfo( $table, $field ) {
00697         $tableName = $this->tableName( $table );
00698         $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
00699         $res = $this->query( $sql, __METHOD__ );
00700         foreach ( $res as $row ) {
00701             if ( $row->name == $field ) {
00702                 return new SQLiteField( $row, $tableName );
00703             }
00704         }
00705 
00706         return false;
00707     }
00708 
00709     protected function doBegin( $fname = '' ) {
00710         if ( $this->mTrxLevel == 1 ) {
00711             $this->commit( __METHOD__ );
00712         }
00713         try {
00714             $this->mConn->beginTransaction();
00715         } catch ( PDOException $e ) {
00716             throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
00717         }
00718         $this->mTrxLevel = 1;
00719     }
00720 
00721     protected function doCommit( $fname = '' ) {
00722         if ( $this->mTrxLevel == 0 ) {
00723             return;
00724         }
00725         try {
00726             $this->mConn->commit();
00727         } catch ( PDOException $e ) {
00728             throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
00729         }
00730         $this->mTrxLevel = 0;
00731     }
00732 
00733     protected function doRollback( $fname = '' ) {
00734         if ( $this->mTrxLevel == 0 ) {
00735             return;
00736         }
00737         $this->mConn->rollBack();
00738         $this->mTrxLevel = 0;
00739     }
00740 
00745     function strencode( $s ) {
00746         return substr( $this->addQuotes( $s ), 1, -1 );
00747     }
00748 
00753     function encodeBlob( $b ) {
00754         return new Blob( $b );
00755     }
00756 
00761     function decodeBlob( $b ) {
00762         if ( $b instanceof Blob ) {
00763             $b = $b->fetch();
00764         }
00765 
00766         return $b;
00767     }
00768 
00773     function addQuotes( $s ) {
00774         if ( $s instanceof Blob ) {
00775             return "x'" . bin2hex( $s->fetch() ) . "'";
00776         } elseif ( is_bool( $s ) ) {
00777             return (int)$s;
00778         } elseif ( strpos( $s, "\0" ) !== false ) {
00779             // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
00780             // This is a known limitation of SQLite's mprintf function which PDO should work around,
00781             // but doesn't. I have reported this to php.net as bug #63419:
00782             // https://bugs.php.net/bug.php?id=63419
00783             // There was already a similar report for SQLite3::escapeString, bug #62361:
00784             // https://bugs.php.net/bug.php?id=62361
00785             return "x'" . bin2hex( $s ) . "'";
00786         } else {
00787             return $this->mConn->quote( $s );
00788         }
00789     }
00790 
00794     function buildLike() {
00795         $params = func_get_args();
00796         if ( count( $params ) > 0 && is_array( $params[0] ) ) {
00797             $params = $params[0];
00798         }
00799 
00800         return parent::buildLike( $params ) . "ESCAPE '\' ";
00801     }
00802 
00806     public function getSearchEngine() {
00807         return "SearchSqlite";
00808     }
00809 
00815     public function deadlockLoop( /*...*/ ) {
00816         $args = func_get_args();
00817         $function = array_shift( $args );
00818 
00819         return call_user_func_array( $function, $args );
00820     }
00821 
00826     protected function replaceVars( $s ) {
00827         $s = parent::replaceVars( $s );
00828         if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
00829             // CREATE TABLE hacks to allow schema file sharing with MySQL
00830 
00831             // binary/varbinary column type -> blob
00832             $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
00833             // no such thing as unsigned
00834             $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
00835             // INT -> INTEGER
00836             $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
00837             // floating point types -> REAL
00838             $s = preg_replace(
00839                 '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
00840                 'REAL',
00841                 $s
00842             );
00843             // varchar -> TEXT
00844             $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
00845             // TEXT normalization
00846             $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
00847             // BLOB normalization
00848             $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
00849             // BOOL -> INTEGER
00850             $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
00851             // DATETIME -> TEXT
00852             $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
00853             // No ENUM type
00854             $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
00855             // binary collation type -> nothing
00856             $s = preg_replace( '/\bbinary\b/i', '', $s );
00857             // auto_increment -> autoincrement
00858             $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
00859             // No explicit options
00860             $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
00861             // AUTOINCREMENT should immedidately follow PRIMARY KEY
00862             $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
00863         } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
00864             // No truncated indexes
00865             $s = preg_replace( '/\(\d+\)/', '', $s );
00866             // No FULLTEXT
00867             $s = preg_replace( '/\bfulltext\b/i', '', $s );
00868         } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
00869             // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
00870             $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
00871         }
00872 
00873         return $s;
00874     }
00875 
00876     public function lock( $lockName, $method, $timeout = 5 ) {
00877         global $wgSQLiteDataDir;
00878 
00879         if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed
00880             if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) {
00881                 throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." );
00882             }
00883         }
00884 
00885         return $this->lockMgr->lock( array( $lockName ), LockManager::LOCK_EX, $timeout )->isOK();
00886     }
00887 
00888     public function unlock( $lockName, $method ) {
00889         return $this->lockMgr->unlock( array( $lockName ), LockManager::LOCK_EX )->isOK();
00890     }
00891 
00898     function buildConcat( $stringList ) {
00899         return '(' . implode( ') || (', $stringList ) . ')';
00900     }
00901 
00902     public function buildGroupConcatField(
00903         $delim, $table, $field, $conds = '', $join_conds = array()
00904     ) {
00905         $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
00906 
00907         return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
00908     }
00909 
00918     function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
00919         $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
00920             $this->addQuotes( $oldName ) . " AND type='table'", $fname );
00921         $obj = $this->fetchObject( $res );
00922         if ( !$obj ) {
00923             throw new MWException( "Couldn't retrieve structure for table $oldName" );
00924         }
00925         $sql = $obj->sql;
00926         $sql = preg_replace(
00927             '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
00928             $this->addIdentifierQuotes( $newName ),
00929             $sql,
00930             1
00931         );
00932         if ( $temporary ) {
00933             if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
00934                 wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
00935             } else {
00936                 $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
00937             }
00938         }
00939 
00940         return $this->query( $sql, $fname );
00941     }
00942 
00951     function listTables( $prefix = null, $fname = __METHOD__ ) {
00952         $result = $this->select(
00953             'sqlite_master',
00954             'name',
00955             "type='table'"
00956         );
00957 
00958         $endArray = array();
00959 
00960         foreach ( $result as $table ) {
00961             $vars = get_object_vars( $table );
00962             $table = array_pop( $vars );
00963 
00964             if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
00965                 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
00966                     $endArray[] = $table;
00967                 }
00968             }
00969         }
00970 
00971         return $endArray;
00972     }
00973 } // end DatabaseSqlite class
00974 
00979 class DatabaseSqliteStandalone extends DatabaseSqlite {
00980     public function __construct( $fileName, $flags = 0 ) {
00981         $this->mFlags = $flags;
00982         $this->tablePrefix( null );
00983         $this->openFile( $fileName );
00984     }
00985 }
00986 
00990 class SQLiteField implements Field {
00991     private $info, $tableName;
00992 
00993     function __construct( $info, $tableName ) {
00994         $this->info = $info;
00995         $this->tableName = $tableName;
00996     }
00997 
00998     function name() {
00999         return $this->info->name;
01000     }
01001 
01002     function tableName() {
01003         return $this->tableName;
01004     }
01005 
01006     function defaultValue() {
01007         if ( is_string( $this->info->dflt_value ) ) {
01008             // Typically quoted
01009             if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
01010                 return str_replace( "''", "'", $this->info->dflt_value );
01011             }
01012         }
01013 
01014         return $this->info->dflt_value;
01015     }
01016 
01020     function isNullable() {
01021         return !$this->info->notnull;
01022     }
01023 
01024     function type() {
01025         return $this->info->type;
01026     }
01027 } // end SQLiteField