Skip to content

Commit

Permalink
[11.x] Add ability to configure SQLite busy_timeout, journal_mode
Browse files Browse the repository at this point in the history
…, and `synchronous` pragmas (#52052)

* Add tests for SQLite `busy_timeout` config option

* Add `busy_timeout` config option

* Add support for setting the SQLite `busy_timeout` connection option

* Wip

* Add tests for setting `journal_mode` and `synchronous`

* Add `journal_mode` and `synchronous`

* Wip

* formatting

---------

Co-authored-by: Taylor Otwell <[email protected]>
  • Loading branch information
bakerkretzmar and taylorotwell authored Jul 10, 2024
1 parent 1a23e8c commit 217789d
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 15 deletions.
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

0 comments on commit 217789d

Please sign in to comment.