Skip to content

Commit 13a2209

Browse files
committed
SQLite improvements
- (BC BREAK) Remove $WAL static property. We instead just let Laravel use its journal_mode config now - Remove journal, wal, and shm files when deleting tenant DB - Check that the system is 64-bit when using NoAttach (we don't build 32 bit extensions) - Use local static instead of a class static property for caching loadExtensionSupported
1 parent 4e22c4d commit 13a2209

File tree

3 files changed

+38
-63
lines changed

3 files changed

+38
-63
lines changed

src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
66

7-
use AssertionError;
87
use Closure;
98
use Illuminate\Database\Eloquent\Model;
109
use PDO;
@@ -19,13 +18,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
1918
*/
2019
public static string|null $path = null;
2120

22-
/**
23-
* Should the WAL journal mode be used for newly created databases.
24-
*
25-
* @see https://www.sqlite.org/pragma.html#pragma_journal_mode
26-
*/
27-
public static bool $WAL = true;
28-
2921
/*
3022
* If this isn't null, a connection to the tenant DB will be created
3123
* and passed to the provided closure, for the purpose of keeping the
@@ -89,26 +81,7 @@ public function createDatabase(TenantWithDatabase $tenant): bool
8981
return true;
9082
}
9183

92-
try {
93-
if (file_put_contents($path = $this->getPath($name), '') === false) {
94-
return false;
95-
}
96-
97-
// todo@sqlite we can just respect Laravel config for WAL now
98-
if (static::$WAL) {
99-
$pdo = new PDO('sqlite:' . $path);
100-
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
101-
102-
// @phpstan-ignore-next-line method.nonObject
103-
assert($pdo->query('pragma journal_mode = wal')->fetch(PDO::FETCH_ASSOC)['journal_mode'] === 'wal', 'Unable to set journal mode to wal.');
104-
}
105-
106-
return true;
107-
} catch (AssertionError $e) {
108-
throw $e;
109-
} catch (Throwable) {
110-
return false;
111-
}
84+
return file_put_contents($this->getPath($name), '') !== false;
11285
}
11386

11487
public function deleteDatabase(TenantWithDatabase $tenant): bool
@@ -123,9 +96,16 @@ public function deleteDatabase(TenantWithDatabase $tenant): bool
12396
return true;
12497
}
12598

99+
$path = $this->getPath($name);
100+
101+
try {
102+
unlink($path.'-journal');
103+
unlink($path.'-wal');
104+
unlink($path.'-shm');
105+
} catch (Throwable) {}
106+
126107
try {
127-
// todo@sqlite we should also remove any other files for the DB e.g. WAL
128-
return unlink($this->getPath($name));
108+
return unlink($path);
129109
} catch (Throwable) {
130110
return false;
131111
}

src/Features/DisallowSqliteAttach.php

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
class DisallowSqliteAttach implements Feature
1515
{
16-
protected static bool|null $loadExtensionSupported = null;
1716
public static string|false|null $extensionPath = null;
1817

1918
public function bootstrap(): void
@@ -38,20 +37,12 @@ public function bootstrap(): void
3837

3938
protected function loadExtension(PDO $pdo): bool
4039
{
41-
if (static::$loadExtensionSupported === null) {
42-
// todo@sqlite refactor to local static
43-
static::$loadExtensionSupported = method_exists($pdo, 'loadExtension');
44-
}
45-
46-
if (static::$loadExtensionSupported === false) {
47-
return false;
48-
}
40+
static $loadExtensionSupported = method_exists($pdo, 'loadExtension');
4941

50-
if (static::$extensionPath === false) {
51-
return false;
52-
}
53-
54-
// todo@sqlite we may want to check for 64 bit
42+
if ((! $loadExtensionSupported) ||
43+
(static::$extensionPath === false) ||
44+
(PHP_INT_SIZE !== 8)
45+
) return false;
5546

5647
$suffix = match (PHP_OS_FAMILY) {
5748
'Linux' => 'so',
@@ -64,9 +55,7 @@ protected function loadExtension(PDO $pdo): bool
6455
$arm = $arch === 'aarch64' || $arch === 'arm64';
6556

6657
static::$extensionPath ??= realpath(base_path('vendor/stancl/tenancy/extensions/lib/' . ($arm ? 'arm/' : '') . 'noattach.' . $suffix));
67-
if (static::$extensionPath === false) {
68-
return false;
69-
}
58+
if (static::$extensionPath === false) return false;
7059

7160
$pdo->loadExtension(static::$extensionPath); // @phpstan-ignore method.notFound
7261

tests/TenantDatabaseManagerTest.php

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager;
3030
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager;
3131
use function Stancl\Tenancy\Tests\pest;
32+
use function Stancl\Tenancy\Tests\withBootstrapping;
33+
use function Stancl\Tenancy\Tests\withTenantDatabases;
3234

3335
beforeEach(function () {
3436
SQLiteDatabaseManager::$path = null;
@@ -146,18 +148,15 @@
146148
expect(database_path('foodb'))->toBe(config('database.connections.tenant.database'));
147149
});
148150

149-
test('sqlite databases use the WAL journal mode by default', function (bool|null $wal) {
150-
$expected = $wal ? 'wal' : 'delete';
151-
if ($wal !== null) {
152-
SQLiteDatabaseManager::$WAL = $wal;
153-
} else {
154-
// default behavior
155-
$expected = 'wal';
156-
}
157-
158-
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
159-
return $event->tenant;
160-
})->toListener());
151+
test('sqlite databases respect the template journal_mode config', function (string $journal_mode) {
152+
withTenantDatabases();
153+
withBootstrapping();
154+
config([
155+
'database.connections.sqlite.journal_mode' => $journal_mode,
156+
'tenancy.bootstrappers' => [
157+
DatabaseTenancyBootstrapper::class,
158+
],
159+
]);
161160

162161
$tenant = Tenant::create([
163162
'tenancy_db_connection' => 'sqlite',
@@ -170,11 +169,18 @@
170169
$db = new PDO('sqlite:' . $dbPath);
171170
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
172171

173-
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($expected);
172+
// Before we connect to the DB using Laravel, it will be in default delete mode
173+
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe('delete');
174+
175+
// This will trigger the logic in Laravel's SQLiteConnector
176+
$tenant->run(fn () => DB::select('select 1'));
177+
178+
$db = new PDO('sqlite:' . $dbPath);
179+
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
174180

175-
// cleanup
176-
SQLiteDatabaseManager::$WAL = true;
177-
})->with([true, false, null]);
181+
// Once we connect to the DB, it will be in the configured journal mode
182+
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($journal_mode);
183+
})->with(['delete', 'wal']);
178184

179185
test('schema manager uses schema to separate tenant dbs', function () {
180186
config([

0 commit comments

Comments
 (0)