1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 13:54:03 +00:00

Tenant DB manager database() -> connection()

This commit is contained in:
Samuel Štancl 2024-09-12 18:34:45 +02:00
parent f3e01c1581
commit 0fc105487b
13 changed files with 60 additions and 60 deletions

View file

@ -83,7 +83,7 @@ class CreateUserWithRLSPolicies extends Command
$manager->setConnection($tenantModel->database()->getTenantHostConnectionName()); $manager->setConnection($tenantModel->database()->getTenantHostConnectionName());
// Set the database name (= central schema name/search_path in this case), username, and password // Set the database name (= central schema name/search_path in this case), username, and password
$tenantModel->setInternal('db_name', $manager->database()->getConfig('search_path')); $tenantModel->setInternal('db_name', $manager->connection()->getConfig('search_path'));
$tenantModel->setInternal('db_username', $username); $tenantModel->setInternal('db_username', $username);
$tenantModel->setInternal('db_password', $password); $tenantModel->setInternal('db_password', $password);

View file

@ -30,7 +30,7 @@ trait ManagesPostgresUsers
$createUser = ! $this->userExists($username); $createUser = ! $this->userExists($username);
if ($createUser) { if ($createUser) {
$this->database()->statement("CREATE USER \"{$username}\" LOGIN PASSWORD '{$password}'"); $this->connection()->statement("CREATE USER \"{$username}\" LOGIN PASSWORD '{$password}'");
} }
$this->grantPermissions($databaseConfig); $this->grantPermissions($databaseConfig);
@ -46,38 +46,38 @@ trait ManagesPostgresUsers
$username = $databaseConfig->getUsername(); $username = $databaseConfig->getUsername();
// Tenant host connection config // Tenant host connection config
$connectionName = $this->database()->getConfig('name'); $connectionName = $this->connection()->getConfig('name');
$centralDatabase = $this->database()->getConfig('database'); $centralDatabase = $this->connection()->getConfig('database');
// Set the DB/schema name to the tenant DB/schema name // Set the DB/schema name to the tenant DB/schema name
config()->set( config()->set(
"database.connections.{$connectionName}", "database.connections.{$connectionName}",
$this->makeConnectionConfig($this->database()->getConfig(), $databaseConfig->getName()), $this->makeConnectionConfig($this->connection()->getConfig(), $databaseConfig->getName()),
); );
// Connect to the tenant DB/schema // Connect to the tenant DB/schema
$this->database()->reconnect(); $this->connection()->reconnect();
// Delete all database objects owned by the user (privileges, tables, views, etc.) // Delete all database objects owned by the user (privileges, tables, views, etc.)
// Postgres users cannot be deleted unless we delete all objects owned by it first // Postgres users cannot be deleted unless we delete all objects owned by it first
$this->database()->statement("DROP OWNED BY \"{$username}\""); $this->connection()->statement("DROP OWNED BY \"{$username}\"");
// Delete the user // Delete the user
$userDeleted = $this->database()->statement("DROP USER \"{$username}\""); $userDeleted = $this->connection()->statement("DROP USER \"{$username}\"");
config()->set( config()->set(
"database.connections.{$connectionName}", "database.connections.{$connectionName}",
$this->makeConnectionConfig($this->database()->getConfig(), $centralDatabase), $this->makeConnectionConfig($this->connection()->getConfig(), $centralDatabase),
); );
// Reconnect to the central database // Reconnect to the central database
$this->database()->reconnect(); $this->connection()->reconnect();
return $userDeleted; return $userDeleted;
} }
public function userExists(string $username): bool public function userExists(string $username): bool
{ {
return (bool) $this->database()->selectOne("SELECT usename FROM pg_user WHERE usename = '{$username}'"); return (bool) $this->connection()->selectOne("SELECT usename FROM pg_user WHERE usename = '{$username}'");
} }
} }

View file

@ -13,7 +13,7 @@ use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
interface StatefulTenantDatabaseManager extends TenantDatabaseManager interface StatefulTenantDatabaseManager extends TenantDatabaseManager
{ {
/** Get the DB connection used by the tenant database manager. */ /** Get the DB connection used by the tenant database manager. */
public function database(): Connection; // todo@dbRefactor rename to connection() public function connection(): Connection;
/** /**
* Set the DB connection that should be used by the tenant database manager. * Set the DB connection that should be used by the tenant database manager.

View file

@ -11,19 +11,19 @@ class MicrosoftSQLDatabaseManager extends TenantDatabaseManager
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
$database = $tenant->database()->getName(); $database = $tenant->database()->getName();
$charset = $this->database()->getConfig('charset'); $charset = $this->connection()->getConfig('charset');
$collation = $this->database()->getConfig('collation'); // todo check why these are not used $collation = $this->connection()->getConfig('collation'); // todo check why these are not used
return $this->database()->statement("CREATE DATABASE [{$database}]"); return $this->connection()->statement("CREATE DATABASE [{$database}]");
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("DROP DATABASE [{$tenant->database()->getName()}]"); return $this->connection()->statement("DROP DATABASE [{$tenant->database()->getName()}]");
} }
public function databaseExists(string $name): bool public function databaseExists(string $name): bool
{ {
return (bool) $this->database()->select("SELECT name FROM master.sys.databases WHERE name = '$name'"); return (bool) $this->connection()->select("SELECT name FROM master.sys.databases WHERE name = '$name'");
} }
} }

View file

@ -11,19 +11,19 @@ class MySQLDatabaseManager extends TenantDatabaseManager
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
$database = $tenant->database()->getName(); $database = $tenant->database()->getName();
$charset = $this->database()->getConfig('charset'); $charset = $this->connection()->getConfig('charset');
$collation = $this->database()->getConfig('collation'); $collation = $this->connection()->getConfig('collation');
return $this->database()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`"); return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("DROP DATABASE `{$tenant->database()->getName()}`"); return $this->connection()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
} }
public function databaseExists(string $name): bool public function databaseExists(string $name): bool
{ {
return (bool) $this->database()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'"); return (bool) $this->connection()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
} }
} }

View file

@ -25,24 +25,24 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
$password = $databaseConfig->getPassword(); $password = $databaseConfig->getPassword();
// Create login // Create login
$this->database()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'"); $this->connection()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'");
// Create user in the database // Create user in the database
// Grant the user permissions specified in the $grants array // Grant the user permissions specified in the $grants array
// The 'CONNECT' permission is granted automatically // The 'CONNECT' permission is granted automatically
$grants = implode(', ', static::$grants); $grants = implode(', ', static::$grants);
return $this->database()->statement("USE [$database]; CREATE USER [$username] FOR LOGIN [$username]; GRANT $grants TO [$username]"); return $this->connection()->statement("USE [$database]; CREATE USER [$username] FOR LOGIN [$username]; GRANT $grants TO [$username]");
} }
public function deleteUser(DatabaseConfig $databaseConfig): bool public function deleteUser(DatabaseConfig $databaseConfig): bool
{ {
return $this->database()->statement("DROP LOGIN [{$databaseConfig->getUsername()}]"); return $this->connection()->statement("DROP LOGIN [{$databaseConfig->getUsername()}]");
} }
public function userExists(string $username): bool public function userExists(string $username): bool
{ {
return (bool) $this->database()->select("SELECT sp.name as username FROM sys.server_principals sp WHERE sp.name = '{$username}'"); return (bool) $this->connection()->select("SELECT sp.name as username FROM sys.server_principals sp WHERE sp.name = '{$username}'");
} }
public function makeConnectionConfig(array $baseConfig, string $databaseName): array public function makeConnectionConfig(array $baseConfig, string $databaseName): array
@ -58,7 +58,7 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
// Set the database to SINGLE_USER mode to ensure that // Set the database to SINGLE_USER mode to ensure that
// No other connections are using the database while we're trying to delete it // No other connections are using the database while we're trying to delete it
// Rollback all active transactions // Rollback all active transactions
$this->database()->statement("ALTER DATABASE [{$tenant->database()->getName()}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"); $this->connection()->statement("ALTER DATABASE [{$tenant->database()->getName()}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
return parent::deleteDatabase($tenant); return parent::deleteDatabase($tenant);
} }

View file

@ -26,7 +26,7 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
$hostname = $databaseConfig->connection()['host']; $hostname = $databaseConfig->connection()['host'];
$password = $databaseConfig->getPassword(); $password = $databaseConfig->getPassword();
$this->database()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'"); $this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
$grants = implode(', ', static::$grants); $grants = implode(', ', static::$grants);
@ -36,24 +36,24 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
$grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`%` IDENTIFIED BY '$password'"; $grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`%` IDENTIFIED BY '$password'";
} }
return $this->database()->statement($grantQuery); return $this->connection()->statement($grantQuery);
} }
protected function isVersion8(): bool protected function isVersion8(): bool
{ {
$versionSelect = (string) $this->database()->raw('select version()')->getValue($this->database()->getQueryGrammar()); $versionSelect = (string) $this->connection()->raw('select version()')->getValue($this->connection()->getQueryGrammar());
$version = $this->database()->select($versionSelect)[0]->{'version()'}; $version = $this->connection()->select($versionSelect)[0]->{'version()'};
return version_compare($version, '8.0.0') >= 0; return version_compare($version, '8.0.0') >= 0;
} }
public function deleteUser(DatabaseConfig $databaseConfig): bool public function deleteUser(DatabaseConfig $databaseConfig): bool
{ {
return $this->database()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'"); return $this->connection()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'");
} }
public function userExists(string $username): bool public function userExists(string $username): bool
{ {
return (bool) $this->database()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'}; return (bool) $this->connection()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'};
} }
} }

View file

@ -21,26 +21,26 @@ class PermissionControlledPostgreSQLDatabaseManager extends PostgreSQLDatabaseMa
$schema = $databaseConfig->connection()['search_path']; $schema = $databaseConfig->connection()['search_path'];
// Host config // Host config
$connectionName = $this->database()->getConfig('name'); $connectionName = $this->connection()->getConfig('name');
$centralDatabase = $this->database()->getConfig('database'); $centralDatabase = $this->connection()->getConfig('database');
$this->database()->statement("GRANT CONNECT ON DATABASE \"{$database}\" TO \"{$username}\""); $this->connection()->statement("GRANT CONNECT ON DATABASE \"{$database}\" TO \"{$username}\"");
// Connect to tenant database // Connect to tenant database
config(["database.connections.{$connectionName}.database" => $database]); config(["database.connections.{$connectionName}.database" => $database]);
$this->database()->reconnect(); $this->connection()->reconnect();
// Grant permissions to create and use tables in the configured schema ("public" by default) to the user // Grant permissions to create and use tables in the configured schema ("public" by default) to the user
$this->database()->statement("GRANT USAGE, CREATE ON SCHEMA {$schema} TO \"{$username}\""); $this->connection()->statement("GRANT USAGE, CREATE ON SCHEMA {$schema} TO \"{$username}\"");
// Grant permissions to use sequences in the current schema to the user // Grant permissions to use sequences in the current schema to the user
$this->database()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA {$schema} TO \"{$username}\""); $this->connection()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA {$schema} TO \"{$username}\"");
// Reconnect to central database // Reconnect to central database
config(["database.connections.{$connectionName}.database" => $centralDatabase]); config(["database.connections.{$connectionName}.database" => $centralDatabase]);
$this->database()->reconnect(); $this->connection()->reconnect();
return true; return true;
} }

View file

@ -23,11 +23,11 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
// Central database name // Central database name
$database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName(); $database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName();
$this->database()->statement("GRANT CONNECT ON DATABASE {$database} TO \"{$username}\""); $this->connection()->statement("GRANT CONNECT ON DATABASE {$database} TO \"{$username}\"");
$this->database()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\""); $this->connection()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\"");
$this->database()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\""); $this->connection()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\"");
$tables = $this->database()->select("SELECT table_name FROM information_schema.tables WHERE table_schema = '{$schema}'"); $tables = $this->connection()->select("SELECT table_name FROM information_schema.tables WHERE table_schema = '{$schema}'");
// Grant permissions to any existing tables. This is used with RLS // Grant permissions to any existing tables. This is used with RLS
// todo@samuel refactor this along with the todo in TenantDatabaseManager // todo@samuel refactor this along with the todo in TenantDatabaseManager
@ -36,7 +36,7 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
$tableName = $table->table_name; $tableName = $table->table_name;
/** @var string $primaryKey */ /** @var string $primaryKey */
$primaryKey = $this->database()->selectOne(<<<SQL $primaryKey = $this->connection()->selectOne(<<<SQL
SELECT column_name SELECT column_name
FROM information_schema.key_column_usage FROM information_schema.key_column_usage
WHERE table_name = '{$tableName}' WHERE table_name = '{$tableName}'
@ -44,11 +44,11 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
SQL)->column_name; SQL)->column_name;
// Grant all permissions for all existing tables // Grant all permissions for all existing tables
$this->database()->statement("GRANT ALL ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\""); $this->connection()->statement("GRANT ALL ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\"");
// Grant permission to reference the primary key for the table // Grant permission to reference the primary key for the table
// The previous query doesn't grant the references privilege, so it has to be granted here // The previous query doesn't grant the references privilege, so it has to be granted here
$this->database()->statement("GRANT REFERENCES (\"{$primaryKey}\") ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\""); $this->connection()->statement("GRANT REFERENCES (\"{$primaryKey}\") ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\"");
} }
return true; return true;

View file

@ -10,16 +10,16 @@ class PostgreSQLDatabaseManager extends TenantDatabaseManager
{ {
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0"); return $this->connection()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0");
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("DROP DATABASE \"{$tenant->database()->getName()}\""); return $this->connection()->statement("DROP DATABASE \"{$tenant->database()->getName()}\"");
} }
public function databaseExists(string $name): bool public function databaseExists(string $name): bool
{ {
return (bool) $this->database()->selectOne("SELECT datname FROM pg_database WHERE datname = '$name'"); return (bool) $this->connection()->selectOne("SELECT datname FROM pg_database WHERE datname = '$name'");
} }
} }

View file

@ -10,17 +10,17 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager
{ {
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\""); return $this->connection()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\"");
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
{ {
return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\" CASCADE"); return $this->connection()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\" CASCADE");
} }
public function databaseExists(string $name): bool public function databaseExists(string $name): bool
{ {
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'"); return (bool) $this->connection()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
} }
public function makeConnectionConfig(array $baseConfig, string $databaseName): array public function makeConnectionConfig(array $baseConfig, string $databaseName): array

View file

@ -14,7 +14,7 @@ abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager
/** The database connection to the server. */ /** The database connection to the server. */
protected string $connection; protected string $connection;
public function database(): Connection public function connection(): Connection
{ {
if (! isset($this->connection)) { if (! isset($this->connection)) {
throw new NoConnectionSetException(static::class); throw new NoConnectionSetException(static::class);

View file

@ -332,7 +332,7 @@ test('database credentials can be provided to PermissionControlledMySQLDatabaseM
/** @var PermissionControlledMySQLDatabaseManager $manager */ /** @var PermissionControlledMySQLDatabaseManager $manager */
$manager = $tenant->database()->manager(); $manager = $tenant->database()->manager();
expect($manager->database()->getConfig('username'))->toBe($username); // user created for the HOST connection expect($manager->connection()->getConfig('username'))->toBe($username); // user created for the HOST connection
expect($manager->userExists($usernameForNewDB))->toBeTrue(); expect($manager->userExists($usernameForNewDB))->toBeTrue();
expect($manager->databaseExists($name))->toBeTrue(); expect($manager->databaseExists($name))->toBeTrue();
}); });
@ -371,7 +371,7 @@ test('tenant database can be created by using the username and password from ten
/** @var MySQLDatabaseManager $manager */ /** @var MySQLDatabaseManager $manager */
$manager = $tenant->database()->manager(); $manager = $tenant->database()->manager();
expect($manager->database()->getConfig('username'))->toBe($username); // user created for the HOST connection expect($manager->connection()->getConfig('username'))->toBe($username); // user created for the HOST connection
expect($manager->databaseExists($name))->toBeTrue(); expect($manager->databaseExists($name))->toBeTrue();
}); });
@ -417,7 +417,7 @@ test('the tenant connection template can be specified either by name or as a con
/** @var MySQLDatabaseManager $manager */ /** @var MySQLDatabaseManager $manager */
$manager = $tenant->database()->manager(); $manager = $tenant->database()->manager();
expect($manager->databaseExists($name))->toBeTrue(); expect($manager->databaseExists($name))->toBeTrue();
expect($manager->database()->getConfig('host'))->toBe('mysql'); expect($manager->connection()->getConfig('host'))->toBe('mysql');
config([ config([
'tenancy.database.template_tenant_connection' => [ 'tenancy.database.template_tenant_connection' => [
@ -446,7 +446,7 @@ test('the tenant connection template can be specified either by name or as a con
/** @var MySQLDatabaseManager $manager */ /** @var MySQLDatabaseManager $manager */
$manager = $tenant->database()->manager(); $manager = $tenant->database()->manager();
expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works
expect($manager->database()->getConfig('host'))->toBe('mysql2'); expect($manager->connection()->getConfig('host'))->toBe('mysql2');
}); });
test('partial tenant connection templates get merged into the central connection template', function () { test('partial tenant connection templates get merged into the central connection template', function () {
@ -471,8 +471,8 @@ test('partial tenant connection templates get merged into the central connection
/** @var MySQLDatabaseManager $manager */ /** @var MySQLDatabaseManager $manager */
$manager = $tenant->database()->manager(); $manager = $tenant->database()->manager();
expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works
expect($manager->database()->getConfig('host'))->toBe('mysql2'); expect($manager->connection()->getConfig('host'))->toBe('mysql2');
expect($manager->database()->getConfig('url'))->toBeNull(); expect($manager->connection()->getConfig('url'))->toBeNull();
}); });
// Datasets // Datasets