Skip to content

Commit 217789d

Browse files
[11.x] Add ability to configure SQLite busy_timeout, journal_mode, 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]>
1 parent 1a23e8c commit 217789d

File tree

7 files changed

+233
-15
lines changed

7 files changed

+233
-15
lines changed

config/database.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
'database' => env('DB_DATABASE', database_path('database.sqlite')),
3838
'prefix' => '',
3939
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
40+
'busy_timeout' => null,
41+
'journal_mode' => null,
42+
'synchronous' => null,
4043
],
4144

4245
'mysql' => [

src/Illuminate/Database/SQLiteConnection.php

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,20 @@ public function __construct($pdo, $database = '', $tablePrefix = '', array $conf
2525
{
2626
parent::__construct($pdo, $database, $tablePrefix, $config);
2727

28-
$enableForeignKeyConstraints = $this->getForeignKeyConstraintsConfigurationValue();
28+
$this->configureForeignKeyConstraints();
29+
$this->configureBusyTimeout();
30+
$this->configureJournalMode();
31+
$this->configureSynchronous();
32+
}
33+
34+
/**
35+
* Enable or disable foreign key constraints if configured.
36+
*
37+
* @return void
38+
*/
39+
protected function configureForeignKeyConstraints(): void
40+
{
41+
$enableForeignKeyConstraints = $this->getConfig('foreign_key_constraints');
2942

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

60+
/**
61+
* Set the busy timeout if configured.
62+
*
63+
* @return void
64+
*/
65+
protected function configureBusyTimeout(): void
66+
{
67+
$milliseconds = $this->getConfig('busy_timeout');
68+
69+
if ($milliseconds === null) {
70+
return;
71+
}
72+
73+
try {
74+
$this->getSchemaBuilder()->setBusyTimeout($milliseconds);
75+
} catch (QueryException $e) {
76+
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
77+
throw $e;
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Set the journal mode if configured.
84+
*
85+
* @return void
86+
*/
87+
protected function configureJournalMode(): void
88+
{
89+
$mode = $this->getConfig('journal_mode');
90+
91+
if ($mode === null) {
92+
return;
93+
}
94+
95+
try {
96+
$this->getSchemaBuilder()->setJournalMode($mode);
97+
} catch (QueryException $e) {
98+
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
99+
throw $e;
100+
}
101+
}
102+
}
103+
104+
/**
105+
* Set the synchronous mode if configured.
106+
*
107+
* @return void
108+
*/
109+
protected function configureSynchronous(): void
110+
{
111+
$mode = $this->getConfig('synchronous');
112+
113+
if ($mode === null) {
114+
return;
115+
}
116+
117+
try {
118+
$this->getSchemaBuilder()->setSynchronous($mode);
119+
} catch (QueryException $e) {
120+
if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
121+
throw $e;
122+
}
123+
}
124+
}
125+
47126
/**
48127
* Escape a binary value for safe SQL embedding.
49128
*
@@ -128,14 +207,4 @@ protected function getDefaultPostProcessor()
128207
{
129208
return new SQLiteProcessor;
130209
}
131-
132-
/**
133-
* Get the database connection foreign key constraints configuration option.
134-
*
135-
* @return bool|null
136-
*/
137-
protected function getForeignKeyConstraintsConfigurationValue()
138-
{
139-
return $this->getConfig('foreign_key_constraints');
140-
}
141210
}

src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connec
591591
*/
592592
public function compileEnableForeignKeyConstraints()
593593
{
594-
return 'PRAGMA foreign_keys = ON;';
594+
return $this->pragma('foreign_keys', 'ON');
595595
}
596596

597597
/**
@@ -601,7 +601,40 @@ public function compileEnableForeignKeyConstraints()
601601
*/
602602
public function compileDisableForeignKeyConstraints()
603603
{
604-
return 'PRAGMA foreign_keys = OFF;';
604+
return $this->pragma('foreign_keys', 'OFF');
605+
}
606+
607+
/**
608+
* Compile the command to set the busy timeout.
609+
*
610+
* @param int $milliseconds
611+
* @return string
612+
*/
613+
public function compileSetBusyTimeout($milliseconds)
614+
{
615+
return $this->pragma('busy_timeout', $milliseconds);
616+
}
617+
618+
/**
619+
* Compile the command to set the journal mode.
620+
*
621+
* @param string $mode
622+
* @return string
623+
*/
624+
public function compileSetJournalMode($mode)
625+
{
626+
return $this->pragma('journal_mode', $mode);
627+
}
628+
629+
/**
630+
* Compile the command to set the synchronous mode.
631+
*
632+
* @param string $mode
633+
* @return string
634+
*/
635+
public function compileSetSynchronous($mode)
636+
{
637+
return $this->pragma('synchronous', $mode);
605638
}
606639

607640
/**
@@ -611,7 +644,7 @@ public function compileDisableForeignKeyConstraints()
611644
*/
612645
public function compileEnableWriteableSchema()
613646
{
614-
return 'PRAGMA writable_schema = 1;';
647+
return $this->pragma('writable_schema', 1);
615648
}
616649

617650
/**
@@ -621,7 +654,19 @@ public function compileEnableWriteableSchema()
621654
*/
622655
public function compileDisableWriteableSchema()
623656
{
624-
return 'PRAGMA writable_schema = 0;';
657+
return $this->pragma('writable_schema', 0);
658+
}
659+
660+
/**
661+
* Get the SQL to set a PRAGMA value.
662+
*
663+
* @param string $name
664+
* @param mixed $value
665+
* @return string
666+
*/
667+
protected function pragma(string $name, mixed $value): string
668+
{
669+
return sprintf('PRAGMA %s = %s;', $name, $value);
625670
}
626671

627672
/**

src/Illuminate/Database/Schema/SQLiteBuilder.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,45 @@ public function dropAllViews()
104104
$this->connection->select($this->grammar->compileRebuild());
105105
}
106106

107+
/**
108+
* Set the busy timeout.
109+
*
110+
* @param int $milliseconds
111+
* @return bool
112+
*/
113+
public function setBusyTimeout($milliseconds)
114+
{
115+
return $this->connection->statement(
116+
$this->grammar->compileSetBusyTimeout($milliseconds)
117+
);
118+
}
119+
120+
/**
121+
* Set the journal mode.
122+
*
123+
* @param string $mode
124+
* @return bool
125+
*/
126+
public function setJournalMode($mode)
127+
{
128+
return $this->connection->statement(
129+
$this->grammar->compileSetJournalMode($mode)
130+
);
131+
}
132+
133+
/**
134+
* Set the synchronous mode.
135+
*
136+
* @param int $mode
137+
* @return bool
138+
*/
139+
public function setSynchronous($mode)
140+
{
141+
return $this->connection->statement(
142+
$this->grammar->compileSetSynchronous($mode)
143+
);
144+
}
145+
107146
/**
108147
* Empty the database file.
109148
*

tests/Database/DatabaseConnectionFactoryTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,27 @@ public function testSqliteForeignKeyConstraints()
144144

145145
$this->assertEquals(1, $this->db->getConnection('constraints_set')->select('PRAGMA foreign_keys')[0]->foreign_keys);
146146
}
147+
148+
public function testSqliteBusyTimeout()
149+
{
150+
$this->db->addConnection([
151+
'url' => 'sqlite:///:memory:?busy_timeout=1234',
152+
], 'busy_timeout_set');
153+
154+
// Can't compare to 0, default value may be something else
155+
$this->assertNotSame(1234, $this->db->getConnection()->select('PRAGMA busy_timeout')[0]->timeout);
156+
157+
$this->assertSame(1234, $this->db->getConnection('busy_timeout_set')->select('PRAGMA busy_timeout')[0]->timeout);
158+
}
159+
160+
public function testSqliteSynchronous()
161+
{
162+
$this->db->addConnection([
163+
'url' => 'sqlite:///:memory:?synchronous=NORMAL',
164+
], 'synchronous_set');
165+
166+
$this->assertSame(2, $this->db->getConnection()->select('PRAGMA synchronous')[0]->synchronous);
167+
168+
$this->assertSame(1, $this->db->getConnection('synchronous_set')->select('PRAGMA synchronous')[0]->synchronous);
169+
}
147170
}

tests/Integration/Database/SchemaBuilderTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,21 @@ public function testAddAndDropPrimaryOnSqlite()
831831
$this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique'));
832832
}
833833

834+
public function testSetJournalModeOnSqlite()
835+
{
836+
if ($this->driver !== 'sqlite') {
837+
$this->markTestSkipped('Test requires a SQLite connection.');
838+
}
839+
840+
file_put_contents(DB::connection('sqlite')->getConfig('database'), '');
841+
842+
$this->assertSame('delete', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode);
843+
844+
Schema::connection('sqlite')->setJournalMode('WAL');
845+
846+
$this->assertSame('wal', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode);
847+
}
848+
834849
public function testAddingMacros()
835850
{
836851
Schema::macro('foo', fn () => 'foo');

tests/Support/ConfigurationUrlParserTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,30 @@ public static function databaseUrls()
257257
'foreign_key_constraints' => true,
258258
],
259259
],
260+
'Sqlite with busy_timeout' => [
261+
'sqlite:////absolute/path/to/database.sqlite?busy_timeout=5000',
262+
[
263+
'driver' => 'sqlite',
264+
'database' => '/absolute/path/to/database.sqlite',
265+
'busy_timeout' => 5000,
266+
],
267+
],
268+
'Sqlite with journal_mode' => [
269+
'sqlite:////absolute/path/to/database.sqlite?journal_mode=WAL',
270+
[
271+
'driver' => 'sqlite',
272+
'database' => '/absolute/path/to/database.sqlite',
273+
'journal_mode' => 'WAL',
274+
],
275+
],
276+
'Sqlite with synchronous' => [
277+
'sqlite:////absolute/path/to/database.sqlite?synchronous=NORMAL',
278+
[
279+
'driver' => 'sqlite',
280+
'database' => '/absolute/path/to/database.sqlite',
281+
'synchronous' => 'NORMAL',
282+
],
283+
],
260284

261285
'Most complex example with read and write subarrays all in string' => [
262286
'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=',

0 commit comments

Comments
 (0)