1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 21:34:04 +00:00

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
This commit is contained in:
Samuel Štancl 2025-09-01 02:07:36 +02:00
parent 4e22c4dd6e
commit 13a2209f11
3 changed files with 38 additions and 63 deletions

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers; namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use AssertionError;
use Closure; use Closure;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use PDO; use PDO;
@ -19,13 +18,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
*/ */
public static string|null $path = null; public static string|null $path = null;
/**
* Should the WAL journal mode be used for newly created databases.
*
* @see https://www.sqlite.org/pragma.html#pragma_journal_mode
*/
public static bool $WAL = true;
/* /*
* If this isn't null, a connection to the tenant DB will be created * If this isn't null, a connection to the tenant DB will be created
* and passed to the provided closure, for the purpose of keeping the * and passed to the provided closure, for the purpose of keeping the
@ -89,26 +81,7 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
return true; return true;
} }
try { return file_put_contents($this->getPath($name), '') !== false;
if (file_put_contents($path = $this->getPath($name), '') === false) {
return false;
}
// todo@sqlite we can just respect Laravel config for WAL now
if (static::$WAL) {
$pdo = new PDO('sqlite:' . $path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// @phpstan-ignore-next-line method.nonObject
assert($pdo->query('pragma journal_mode = wal')->fetch(PDO::FETCH_ASSOC)['journal_mode'] === 'wal', 'Unable to set journal mode to wal.');
}
return true;
} catch (AssertionError $e) {
throw $e;
} catch (Throwable) {
return false;
}
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
@ -123,9 +96,16 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
return true; return true;
} }
$path = $this->getPath($name);
try { try {
// todo@sqlite we should also remove any other files for the DB e.g. WAL unlink($path.'-journal');
return unlink($this->getPath($name)); unlink($path.'-wal');
unlink($path.'-shm');
} catch (Throwable) {}
try {
return unlink($path);
} catch (Throwable) { } catch (Throwable) {
return false; return false;
} }

View file

@ -13,7 +13,6 @@ use Stancl\Tenancy\Contracts\Feature;
class DisallowSqliteAttach implements Feature class DisallowSqliteAttach implements Feature
{ {
protected static bool|null $loadExtensionSupported = null;
public static string|false|null $extensionPath = null; public static string|false|null $extensionPath = null;
public function bootstrap(): void public function bootstrap(): void
@ -38,20 +37,12 @@ class DisallowSqliteAttach implements Feature
protected function loadExtension(PDO $pdo): bool protected function loadExtension(PDO $pdo): bool
{ {
if (static::$loadExtensionSupported === null) { static $loadExtensionSupported = method_exists($pdo, 'loadExtension');
// todo@sqlite refactor to local static
static::$loadExtensionSupported = method_exists($pdo, 'loadExtension');
}
if (static::$loadExtensionSupported === false) { if ((! $loadExtensionSupported) ||
return false; (static::$extensionPath === false) ||
} (PHP_INT_SIZE !== 8)
) return false;
if (static::$extensionPath === false) {
return false;
}
// todo@sqlite we may want to check for 64 bit
$suffix = match (PHP_OS_FAMILY) { $suffix = match (PHP_OS_FAMILY) {
'Linux' => 'so', 'Linux' => 'so',
@ -64,9 +55,7 @@ class DisallowSqliteAttach implements Feature
$arm = $arch === 'aarch64' || $arch === 'arm64'; $arm = $arch === 'aarch64' || $arch === 'arm64';
static::$extensionPath ??= realpath(base_path('vendor/stancl/tenancy/extensions/lib/' . ($arm ? 'arm/' : '') . 'noattach.' . $suffix)); static::$extensionPath ??= realpath(base_path('vendor/stancl/tenancy/extensions/lib/' . ($arm ? 'arm/' : '') . 'noattach.' . $suffix));
if (static::$extensionPath === false) { if (static::$extensionPath === false) return false;
return false;
}
$pdo->loadExtension(static::$extensionPath); // @phpstan-ignore method.notFound $pdo->loadExtension(static::$extensionPath); // @phpstan-ignore method.notFound

View file

@ -29,6 +29,8 @@ use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQ
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager; use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager;
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager; use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager;
use function Stancl\Tenancy\Tests\pest; use function Stancl\Tenancy\Tests\pest;
use function Stancl\Tenancy\Tests\withBootstrapping;
use function Stancl\Tenancy\Tests\withTenantDatabases;
beforeEach(function () { beforeEach(function () {
SQLiteDatabaseManager::$path = null; SQLiteDatabaseManager::$path = null;
@ -146,18 +148,15 @@ test('db name is prefixed with db path when sqlite is used', function () {
expect(database_path('foodb'))->toBe(config('database.connections.tenant.database')); expect(database_path('foodb'))->toBe(config('database.connections.tenant.database'));
}); });
test('sqlite databases use the WAL journal mode by default', function (bool|null $wal) { test('sqlite databases respect the template journal_mode config', function (string $journal_mode) {
$expected = $wal ? 'wal' : 'delete'; withTenantDatabases();
if ($wal !== null) { withBootstrapping();
SQLiteDatabaseManager::$WAL = $wal; config([
} else { 'database.connections.sqlite.journal_mode' => $journal_mode,
// default behavior 'tenancy.bootstrappers' => [
$expected = 'wal'; DatabaseTenancyBootstrapper::class,
} ],
]);
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
$tenant = Tenant::create([ $tenant = Tenant::create([
'tenancy_db_connection' => 'sqlite', 'tenancy_db_connection' => 'sqlite',
@ -170,11 +169,18 @@ test('sqlite databases use the WAL journal mode by default', function (bool|null
$db = new PDO('sqlite:' . $dbPath); $db = new PDO('sqlite:' . $dbPath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($expected); // Before we connect to the DB using Laravel, it will be in default delete mode
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe('delete');
// cleanup // This will trigger the logic in Laravel's SQLiteConnector
SQLiteDatabaseManager::$WAL = true; $tenant->run(fn () => DB::select('select 1'));
})->with([true, false, null]);
$db = new PDO('sqlite:' . $dbPath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Once we connect to the DB, it will be in the configured journal mode
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($journal_mode);
})->with(['delete', 'wal']);
test('schema manager uses schema to separate tenant dbs', function () { test('schema manager uses schema to separate tenant dbs', function () {
config([ config([