database()->getName(); if ($this->isInMemory($name)) { // If :memory: is used, we update the tenant with a *named* in-memory SQLite connection. // // This makes use of the package feasible with in-memory SQLite. Pure :memory: isn't // sufficient since the tenant creation process involves constant creation and destruction // of the tenant connection, always clearing the memory (like migrations). Additionally, // tenancy()->central() calls would close the database since at the moment we close the // tenant connection (to prevent accidental references to it in the central context) when // tenancy is ended. // // Note that named in-memory databases DO NOT have process lifetime. You need an open // PDO connection to keep the memory from being cleaned up. It's up to the user how they // handle this, common solutions may involve storing the connection in the service container // or creating a closure holding a reference to it and passing that to register_shutdown_function(). $name = '_tenancy_inmemory_' . $tenant->getTenantKey(); $tenant->setInternal('db_name', "file:$name?mode=memory&cache=shared"); $tenant->save(); return true; } return file_put_contents($this->getPath($name), '') !== false; } public function deleteDatabase(TenantWithDatabase $tenant): bool { $name = $tenant->database()->getName(); if ($this->isInMemory($name)) { if (static::$closeInMemoryConnectionUsing) { (static::$closeInMemoryConnectionUsing)($tenant); } return true; } $path = $this->getPath($name); try { unlink($path . '-journal'); unlink($path . '-wal'); unlink($path . '-shm'); } catch (Throwable) {} try { return unlink($path); } catch (Throwable) { return false; } } public function databaseExists(string $name): bool { return $this->isInMemory($name) || file_exists($this->getPath($name)); } public function makeConnectionConfig(array $baseConfig, string $databaseName): array { if ($this->isInMemory($databaseName)) { // Named in-memory DBs are formatted like 'file:_tenancy_inmemory_tenant123?mode=memory&cache=shared' $this->validateDatabaseName($databaseName, extraAllowedCharacters: ':?=&'); $baseConfig['database'] = $databaseName; if (static::$persistInMemoryConnectionUsing !== null) { $dsn = "sqlite:$databaseName"; (static::$persistInMemoryConnectionUsing)(new PDO($dsn), $dsn); } } else { $baseConfig['database'] = $this->getPath($databaseName); } return $baseConfig; } public function getCurrentDatabaseName(Connection $connection): string { return $connection->getDatabaseName(); } public function getPath(string $name): string { $this->validateDatabaseName($name); if (static::$path) { return rtrim(static::$path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name; } return database_path($name); } public static function isInMemory(string $name): bool { $isNamed = str_starts_with($name, 'file:_tenancy_inmemory_') && str_ends_with($name, '?mode=memory&cache=shared'); return $name === ':memory:' || $isNamed; } /** * Ensure database name only contains allowed characters * (allowedDatabaseNameCharacters() + $extraAllowedCharacters) and is not a directory name. * * @throws InvalidArgumentException */ protected function validateDatabaseName(string $name, string $extraAllowedCharacters = ''): void { $this->validateParameter($name, static::$allowedDatabaseNameCharacters . $extraAllowedCharacters); if ($name === '') { throw new InvalidArgumentException('Database name cannot be empty.'); } if (is_dir($name)) { throw new InvalidArgumentException('Database name cannot be a directory.'); } } }