/** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
+ /** @var string[] See https://www.sqlite.org/lang_transaction.html */
+ private static $VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
+
/**
* Additional params include:
* - dbDirectory : directory containing the DB and the lock file directory
* - dbFilePath : use this to force the path of the DB file
* - trxMode : one of (deferred, immediate, exclusive)
- * @param array $p
+ * @param array $params
*/
- public function __construct( array $p ) {
- if ( isset( $p['dbFilePath'] ) ) {
- $this->dbPath = $p['dbFilePath'];
- if ( !strlen( $p['dbname'] ) ) {
- $p['dbname'] = self::generateDatabaseName( $this->dbPath );
+ public function __construct( array $params ) {
+ if ( isset( $params['dbFilePath'] ) ) {
+ $this->dbPath = $params['dbFilePath'];
+ if ( !strlen( $params['dbname'] ) ) {
+ $params['dbname'] = self::generateDatabaseName( $this->dbPath );
}
- } elseif ( isset( $p['dbDirectory'] ) ) {
- $this->dbDir = $p['dbDirectory'];
+ } elseif ( isset( $params['dbDirectory'] ) ) {
+ $this->dbDir = $params['dbDirectory'];
}
- // Set a dummy user to make initConnection() trigger open()
- parent::__construct( [ 'user' => '@' ] + $p );
+ parent::__construct( $params );
- $this->trxMode = strtoupper( $p['trxMode'] ?? '' );
+ $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
$lockDirectory = $this->getLockFileDirectory();
if ( $lockDirectory !== null ) {
}
protected static function getAttributes() {
- return [ self::ATTR_DB_LEVEL_LOCKING => true ];
+ return [
+ self::ATTR_DB_IS_FILE => true,
+ self::ATTR_DB_LEVEL_LOCKING => true
+ ];
}
/**
return 'sqlite';
}
- /**
- * @todo Check if it should be true like parent class
- *
- * @return bool
- */
- public function implicitGroupby() {
- return false;
- }
-
protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
$this->close();
// Note that for SQLite, $server, $user, and $pass are ignored
if ( $schema !== null ) {
- throw new DBExpectedError( $this, __CLASS__ . ": cannot use schemas ('$schema')" );
+ throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
}
if ( $this->dbPath !== null ) {
} elseif ( $this->dbDir !== null ) {
$path = self::generateFileName( $this->dbDir, $dbName );
} else {
- throw new DBExpectedError( $this, __CLASS__ . ": DB path or directory required" );
+ throw $this->newExceptionAfterConnectError( "DB path or directory required" );
}
- if ( !in_array( $this->trxMode, [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ], true ) ) {
- throw new DBExpectedError(
- $this,
- __CLASS__ . ": invalid transaction mode '{$this->trxMode}'"
- );
+ // Check if the database file already exists but is non-readable
+ if (
+ !self::isProcessMemoryPath( $path ) &&
+ file_exists( $path ) &&
+ !is_readable( $path )
+ ) {
+ throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
+ } elseif ( !in_array( $this->trxMode, self::$VALID_TRX_MODES, true ) ) {
+ throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
}
- if ( !self::isProcessMemoryPath( $path ) && !is_readable( $path ) ) {
- $error = "SQLite database file not readable";
- $this->connLogger->error(
- "Error connecting to {db_server}: {error}",
- $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
- );
- throw new DBConnectionError( $this, $error );
+ $attributes = [];
+ if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
+ // Persistent connections can avoid some schema index reading overhead.
+ // On the other hand, they can cause horrible contention with DBO_TRX.
+ if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
+ $this->connLogger->warning(
+ __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
+ $this->getLogContext()
+ );
+ } else {
+ $attributes[PDO::ATTR_PERSISTENT] = true;
+ }
}
try {
- $conn = new PDO(
- "sqlite:$path",
- '',
- '',
- [ PDO::ATTR_PERSISTENT => (bool)( $this->flags & self::DBO_PERSISTENT ) ]
- );
- // Set error codes only, don't raise exceptions
- $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+ // Open the database file, creating it if it does not yet exist
+ $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
} catch ( PDOException $e ) {
- $error = $e->getMessage();
- $this->connLogger->error(
- "Error connecting to {db_server}: {error}",
- $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
- );
- throw new DBConnectionError( $this, $error );
+ throw $this->newExceptionAfterConnectError( $e->getMessage() );
}
- $this->conn = $conn;
$this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
try {
$flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY;
// Enforce LIKE to be case sensitive, just like MySQL
$this->query( 'PRAGMA case_sensitive_like = 1', __METHOD__, $flags );
- // Apply an optimizations or requirements regarding fsync() usage
+ // Apply optimizations or requirements regarding fsync() usage
$sync = $this->connectionVariables['synchronous'] ?? null;
if ( in_array( $sync, [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ], true ) ) {
$this->query( "PRAGMA synchronous = $sync", __METHOD__, $flags );
}
} catch ( Exception $e ) {
- // Connection was not fully initialized and is not safe for use
- $this->conn = false;
- throw $e;
+ throw $this->newExceptionAfterConnectError( $e->getMessage() );
}
}
return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
}
- /**
- * Check if the searchindext table is FTS enabled.
- * @return bool False if not enabled.
- */
- public function checkForEnabledSearch() {
- if ( self::$fulltextEnabled === null ) {
- self::$fulltextEnabled = false;
- $table = $this->tableName( 'searchindex' );
- $res = $this->query(
- "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'",
- __METHOD__,
- self::QUERY_IGNORE_DBO_TRX
- );
- if ( $res ) {
- $row = $res->fetchRow();
- self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
- }
- }
-
- return self::$fulltextEnabled;
- }
-
/**
* Returns version of currently supported SQLite fulltext search module or false if none present.
* @return string
return false;
}
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError(
+ $this,
+ __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
+ );
+ }
+
+ $database = $domain->getDatabase();
+ // A null database means "don't care" so leave it as is and update the table prefix
+ if ( $database === null ) {
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ null,
+ $domain->getTablePrefix()
+ );
+
+ return true;
+ }
+
+ if ( $database !== $this->getDBname() ) {
+ throw new DBExpectedError(
+ $this,
+ __CLASS__ . ": cannot change database (got '$database')"
+ );
+ }
+
+ return true;
+ }
+
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*
}
public function serverIsReadOnly() {
+ $this->assertHasConnectionHandle();
+
$path = $this->getDbFilePath();
return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );