Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Add ability to configure SQLite busy_timeout, journal_mode, and synchronous pragmas #52052

Merged
merged 8 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
],

'mysql' => [
Expand Down
91 changes: 80 additions & 11 deletions src/Illuminate/Database/SQLiteConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,20 @@ public function __construct($pdo, $database = '', $tablePrefix = '', array $conf
{
parent::__construct($pdo, $database, $tablePrefix, $config);

$enableForeignKeyConstraints = $this->getForeignKeyConstraintsConfigurationValue();
$this->configureForeignKeyConstraints();
$this->configureBusyTimeout();
$this->configureJournalMode();
$this->configureSynchronous();
}

/**
* Enable or disable foreign key constraints if configured.
*
* @return void
*/
protected function configureForeignKeyConstraints(): void
{
$enableForeignKeyConstraints = $this->getConfig('foreign_key_constraints');

if ($enableForeignKeyConstraints === null) {
return;
Expand All @@ -44,6 +57,72 @@ public function __construct($pdo, $database = '', $tablePrefix = '', array $conf
}
}

/**
* Set the busy timeout if configured.
*
* @return void
*/
protected function configureBusyTimeout(): void
{
$milliseconds = $this->getConfig('busy_timeout');

if ($milliseconds === null) {
return;
}

try {
$this->getSchemaBuilder()->setBusyTimeout($milliseconds);
} catch (QueryException $e) {
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
throw $e;
}
}
}

/**
* Set the journal mode if configured.
*
* @return void
*/
protected function configureJournalMode(): void
{
$mode = $this->getConfig('journal_mode');

if ($mode === null) {
return;
}

try {
$this->getSchemaBuilder()->setJournalMode($mode);
} catch (QueryException $e) {
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
throw $e;
}
}
}

/**
* Set the synchronous mode if configured.
*
* @return void
*/
protected function configureSynchronous(): void
{
$mode = $this->getConfig('synchronous');

if ($mode === null) {
return;
}

try {
$this->getSchemaBuilder()->setSynchronous($mode);
} catch (QueryException $e) {
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
throw $e;
}
}
}

/**
* Escape a binary value for safe SQL embedding.
*
Expand Down Expand Up @@ -128,14 +207,4 @@ protected function getDefaultPostProcessor()
{
return new SQLiteProcessor;
}

/**
* Get the database connection foreign key constraints configuration option.
*
* @return bool|null
*/
protected function getForeignKeyConstraintsConfigurationValue()
{
return $this->getConfig('foreign_key_constraints');
}
}
53 changes: 49 additions & 4 deletions src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connec
*/
public function compileEnableForeignKeyConstraints()
{
return 'PRAGMA foreign_keys = ON;';
return $this->pragma('foreign_keys', 'ON');
}

/**
Expand All @@ -601,7 +601,40 @@ public function compileEnableForeignKeyConstraints()
*/
public function compileDisableForeignKeyConstraints()
{
return 'PRAGMA foreign_keys = OFF;';
return $this->pragma('foreign_keys', 'OFF');
}

/**
* Compile the command to set the busy timeout.
*
* @param int $milliseconds
* @return string
*/
public function compileSetBusyTimeout($milliseconds)
{
return $this->pragma('busy_timeout', $milliseconds);
}

/**
* Compile the command to set the journal mode.
*
* @param string $mode
* @return string
*/
public function compileSetJournalMode($mode)
{
return $this->pragma('journal_mode', $mode);
}

/**
* Compile the command to set the synchronous mode.
*
* @param string $mode
* @return string
*/
public function compileSetSynchronous($mode)
{
return $this->pragma('synchronous', $mode);
}

/**
Expand All @@ -611,7 +644,7 @@ public function compileDisableForeignKeyConstraints()
*/
public function compileEnableWriteableSchema()
{
return 'PRAGMA writable_schema = 1;';
return $this->pragma('writable_schema', 1);
}

/**
Expand All @@ -621,7 +654,19 @@ public function compileEnableWriteableSchema()
*/
public function compileDisableWriteableSchema()
{
return 'PRAGMA writable_schema = 0;';
return $this->pragma('writable_schema', 0);
}

/**
* Get the SQL to set a PRAGMA value.
*
* @param string $name
* @param mixed $value
* @return string
*/
protected function pragma(string $name, mixed $value): string
{
return sprintf('PRAGMA %s = %s;', $name, $value);
}

/**
Expand Down
39 changes: 39 additions & 0 deletions src/Illuminate/Database/Schema/SQLiteBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,45 @@ public function dropAllViews()
$this->connection->select($this->grammar->compileRebuild());
}

/**
* Set the busy timeout.
*
* @param int $milliseconds
* @return bool
*/
public function setBusyTimeout($milliseconds)
{
return $this->connection->statement(
$this->grammar->compileSetBusyTimeout($milliseconds)
);
}

/**
* Set the journal mode.
*
* @param string $mode
* @return bool
*/
public function setJournalMode($mode)
{
return $this->connection->statement(
$this->grammar->compileSetJournalMode($mode)
);
}

/**
* Set the synchronous mode.
*
* @param int $mode
* @return bool
*/
public function setSynchronous($mode)
{
return $this->connection->statement(
$this->grammar->compileSetSynchronous($mode)
);
}

/**
* Empty the database file.
*
Expand Down
23 changes: 23 additions & 0 deletions tests/Database/DatabaseConnectionFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,27 @@ public function testSqliteForeignKeyConstraints()

$this->assertEquals(1, $this->db->getConnection('constraints_set')->select('PRAGMA foreign_keys')[0]->foreign_keys);
}

public function testSqliteBusyTimeout()
{
$this->db->addConnection([
'url' => 'sqlite:///:memory:?busy_timeout=1234',
], 'busy_timeout_set');

// Can't compare to 0, default value may be something else
$this->assertNotSame(1234, $this->db->getConnection()->select('PRAGMA busy_timeout')[0]->timeout);

$this->assertSame(1234, $this->db->getConnection('busy_timeout_set')->select('PRAGMA busy_timeout')[0]->timeout);
}

public function testSqliteSynchronous()
{
$this->db->addConnection([
'url' => 'sqlite:///:memory:?synchronous=NORMAL',
], 'synchronous_set');

$this->assertSame(2, $this->db->getConnection()->select('PRAGMA synchronous')[0]->synchronous);

$this->assertSame(1, $this->db->getConnection('synchronous_set')->select('PRAGMA synchronous')[0]->synchronous);
}
}
15 changes: 15 additions & 0 deletions tests/Integration/Database/SchemaBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,21 @@ public function testAddAndDropPrimaryOnSqlite()
$this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique'));
}

public function testSetJournalModeOnSqlite()
{
if ($this->driver !== 'sqlite') {
$this->markTestSkipped('Test requires a SQLite connection.');
}

file_put_contents(DB::connection('sqlite')->getConfig('database'), '');

$this->assertSame('delete', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode);

Schema::connection('sqlite')->setJournalMode('WAL');

$this->assertSame('wal', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode);
}

public function testAddingMacros()
{
Schema::macro('foo', fn () => 'foo');
Expand Down
24 changes: 24 additions & 0 deletions tests/Support/ConfigurationUrlParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,30 @@ public static function databaseUrls()
'foreign_key_constraints' => true,
],
],
'Sqlite with busy_timeout' => [
'sqlite:////absolute/path/to/database.sqlite?busy_timeout=5000',
[
'driver' => 'sqlite',
'database' => '/absolute/path/to/database.sqlite',
'busy_timeout' => 5000,
],
],
'Sqlite with journal_mode' => [
'sqlite:////absolute/path/to/database.sqlite?journal_mode=WAL',
[
'driver' => 'sqlite',
'database' => '/absolute/path/to/database.sqlite',
'journal_mode' => 'WAL',
],
],
'Sqlite with synchronous' => [
'sqlite:////absolute/path/to/database.sqlite?synchronous=NORMAL',
[
'driver' => 'sqlite',
'database' => '/absolute/path/to/database.sqlite',
'synchronous' => 'NORMAL',
],
],

'Most complex example with read and write subarrays all in string' => [
'mysql://root:@null/database?read[host][]=192.168.1.1&write[host][]=196.168.1.2&sticky=true&charset=utf8mb4&collation=utf8mb4_unicode_ci&prefix=',
Expand Down