Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
808f527
Use select() instead of selectOne() in databaseExists() and userExists()
lukinovec Apr 29, 2026
ad7d229
Use parameter binding in SELECT queries
lukinovec Apr 29, 2026
bdf592c
Add parameter validation to DB managers
lukinovec Apr 29, 2026
5adbc14
Test SQL parameter validation
lukinovec Apr 29, 2026
182f3a2
Fix code style (php-cs-fixer)
github-actions[bot] Apr 29, 2026
d5087d1
Extract parameter validation into a trait
lukinovec Apr 29, 2026
db03997
Validate SQLite DB names in create/deleteDatabase()
lukinovec Apr 29, 2026
0fdb8b2
Validate user passwords in DB managers
lukinovec Apr 29, 2026
4a3e6ba
Test invalid passwords, improve test name and comments
lukinovec Apr 29, 2026
740d53e
Rename ValidatesSqlParameters to ValidatesDatabaseParameters
lukinovec Apr 29, 2026
8592949
Improve ValidatesDatabaseParameters docblocks
lukinovec Apr 29, 2026
f3f1ab9
Skip null parameters in validateParameter
lukinovec Apr 30, 2026
75b74f2
Make validateParameter have void return type
lukinovec Apr 30, 2026
322257f
Validate SQLite filename in databaseExists
lukinovec Apr 30, 2026
46f73c4
Improve ValidatesDatabaseParameters comments, delete extra early return
lukinovec Apr 30, 2026
4bdb877
Cover null parameter skipping
lukinovec Apr 30, 2026
50ea524
Simplify test, improve comments
lukinovec Apr 30, 2026
bacbf93
Improve validation exception message
lukinovec Apr 30, 2026
37a4c7d
Check if paremeter is string
lukinovec Apr 30, 2026
2bd3a86
Quote database parameter in GRANT statement for consistency
lukinovec Apr 30, 2026
76c324d
Add `validateFilename()`
lukinovec May 1, 2026
d3607f8
Use 'allowedCharacters' instead of 'allowlist', code quality
lukinovec May 1, 2026
e8168eb
Add string check to validateFilename, swap validation order
lukinovec May 1, 2026
9611a05
Skip null parameters, throw for other non-string parameters
lukinovec May 1, 2026
f3836cc
Fix code style (php-cs-fixer)
github-actions[bot] May 1, 2026
2bdda23
Disallow empty strings as filenames
lukinovec May 1, 2026
1a01164
Make validateFilename accept string instead of ?string
lukinovec May 1, 2026
665404e
Add `DatabaseTenancyBootstrapper::$harden`
lukinovec May 1, 2026
fbd1e02
Correct DatabaseTenancyBootstrapper test filename
lukinovec May 1, 2026
fc6a931
Fix code style (php-cs-fixer)
github-actions[bot] May 1, 2026
f5f5f1d
Fix DB bootstrapper test
lukinovec May 1, 2026
52f6857
If harden throws an exception, revert connection back to central
lukinovec May 1, 2026
0ce3d86
DATABASE_URL test: set config for both datasets
lukinovec May 1, 2026
2ae1f79
Cover empty string parameters
lukinovec May 1, 2026
b1f0d0a
Get central DB from config in harden test
lukinovec May 1, 2026
7363318
Make in-memory DB detection more strict
lukinovec May 1, 2026
48b4837
Validate in-memory db names, move SQLite-specific methods to the SQLi…
lukinovec May 1, 2026
7683bef
Fix code style (php-cs-fixer)
github-actions[bot] May 1, 2026
e48d822
Validate SQLite DB name unconditionally in getPath()
lukinovec May 1, 2026
9a9adc0
Use getPath() in makeConnectionConfig()
lukinovec May 1, 2026
7f93f44
Test that the SQLite DB manager recognizes in-memory DBs
lukinovec May 1, 2026
7660ddd
Improve readability of harden() call
lukinovec May 1, 2026
26c161a
Add regression test for makeConnectionConfig not working correctly wi…
lukinovec May 1, 2026
429e098
Improve code quality and comments
lukinovec May 1, 2026
ea20eb1
Validate in-memory DBs outside of isInMemory
lukinovec May 1, 2026
405aaaf
Handle MySQL charset and collation
lukinovec May 4, 2026
2b3466f
Check the current DB name instead of configured one in harden()
lukinovec May 4, 2026
338526d
Query for MySQL defaults instead of assuming them in charset test
lukinovec May 4, 2026
fec170a
Fix code style (php-cs-fixer)
github-actions[bot] May 4, 2026
98a808b
Quote schema names in GRANT statements
lukinovec May 4, 2026
6ed9975
Catch broader range of exceptions (harden() in DB bootstrapper)
lukinovec May 4, 2026
de91348
Specify exception message in assertions
lukinovec May 4, 2026
bdbfbd4
Remove extra variable
lukinovec May 4, 2026
e59195e
Improve coverage
lukinovec May 4, 2026
66ae88a
Fix non-string parameter validation assertion
lukinovec May 4, 2026
0331875
Specify charset and collation config in test
lukinovec May 4, 2026
bbd8f6f
Add parentheses to instanceof check
lukinovec May 4, 2026
587f347
Restore default charset after assertion
lukinovec May 4, 2026
099a666
Add valid password assertion
lukinovec May 4, 2026
649c802
Use unique DB names and passwords in test
lukinovec May 4, 2026
519c819
Delete user created in validation test
lukinovec May 5, 2026
d9ae274
Delete redundant cleanup
lukinovec May 5, 2026
9055b61
Merge branch 'master' into validate-sql-parameters
stancl Jun 7, 2026
6e82a9e
Change @mixin annotations to @see
lukinovec Jun 8, 2026
b4244be
Determine data column and internal prefix dynamically instead of hard…
lukinovec Jun 8, 2026
42a2c8e
Improve `$harden` annotation
lukinovec Jun 8, 2026
b7045c5
Rename harden() to verifyTenantCanUseDatabase()
lukinovec Jun 8, 2026
4386a3b
Improve annotations in ValidatesDatabaseParameters
lukinovec Jun 8, 2026
9ea085e
Improve wording
lukinovec Jun 8, 2026
f9636b1
Use `Arr::wrap` instead of `(array)`
lukinovec Jun 8, 2026
b3111f1
Name second param passed to validateDatabaseName for clarity
lukinovec Jun 8, 2026
407197b
Use datasets in hardening tests
lukinovec Jun 8, 2026
49356a5
Use more specific exception assertions
lukinovec Jun 8, 2026
cf7e086
Clean up `SQLiteDatabaseManager::$path` in `afterEach`
lukinovec Jun 8, 2026
d28abde
Delete redundant db username config
lukinovec Jun 8, 2026
36782eb
Move mysql charset/collation validation assertions to a dedicated test
lukinovec Jun 8, 2026
3fb0176
improve mysql charset/collation validation test
stancl Jun 8, 2026
88156d1
improve test name
stancl Jun 8, 2026
b3d1158
cast numeric params to string params
stancl Jun 8, 2026
13e32dd
update docblock on $harden
stancl Jun 8, 2026
fbffeb8
improve docblocks for allowlists
stancl Jun 8, 2026
48b8aac
Consider null parameters invalid
lukinovec Jun 9, 2026
565bc41
Use a more specific central db check in the hardening feature
lukinovec Jun 9, 2026
7972da5
Fix code style (php-cs-fixer)
github-actions[bot] Jun 9, 2026
540e363
Improve hardening
lukinovec Jun 9, 2026
93f77a5
Fix PHPStan error
lukinovec Jun 9, 2026
1ae7d58
Convert allowlist methods into static properties
lukinovec Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/Bootstrappers/DatabaseTenancyBootstrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,42 @@
namespace Stancl\Tenancy\Bootstrappers;

use Exception;
use Illuminate\Support\Facades\DB;
use RuntimeException;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
use Throwable;

class DatabaseTenancyBootstrapper implements TenancyBootstrapper
{
/**
* When true, throw an exception if a tenant gets connected to
* another tenant's database or to the central database.
*
* This case should never come up in well-configured apps where
* users cannot set or edit tenant IDs or database names, so this
* option is disabled by default.
*
* However, applications dealing with extremely sensitive data may
* choose to enable this runtime check to prevent a bug or misconfiguration
* from creating an exploit that would let an attacker access another
* tenant's data or data from the central database.
*
* One way such a scenario might come up is if an application allows
* broad tenant attribute updates on a page for updating some fields
* on the tenant, without restricting that action to only a limited
* set of fields that are safe to edit. An attacker might be able to add
* something like ['tenancy_db_name' => '...'] to the request which could
* lead to this internal attribute being updated on an existing tenant.
*
* It's possible that enabling this setting will negate the performance
* benefits of cached tenant lookup.
*/
public static bool $harden = false;
Comment thread
lukinovec marked this conversation as resolved.

/** @var DatabaseManager */
protected $database;

Expand Down Expand Up @@ -41,10 +69,43 @@ public function bootstrap(Tenant $tenant): void
}

$this->database->connectToTenant($tenant);

if (static::$harden) {
try {
$this->verifyTenantCanUseDatabase($tenant);
} catch (Throwable $e) {
// Revert connection back to central
$this->revert();

throw $e;
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

public function revert(): void
{
$this->database->reconnectToCentral();
}

protected function verifyTenantCanUseDatabase(Tenant $tenant): void
{
/** @var \Stancl\Tenancy\Database\Models\Tenant&TenantWithDatabase $tenant */
$tenantDbName = $tenant->database()->getName();

// Check if any other tenant uses this tenant's database
if ($tenant::where($tenant->getTenantKeyName(), '!=', $tenant->getTenantKey())
->where($tenant::getDataColumn() . '->' . $tenant->internalPrefix() . 'db_name', $tenantDbName)
->exists()) {
throw new RuntimeException('Tenant cannot use a database of another tenant.');
}

// Check if the current database is not the central database
$centralDbName = DB::connection(
config('tenancy.database.central_connection', 'central')
)->getDatabaseName();

if (DB::getDatabaseName() === $centralDbName) {
throw new RuntimeException('Tenant cannot use the central database.');
}
}
}
7 changes: 6 additions & 1 deletion src/Database/Concerns/ManagesPostgresUsers.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public function createUser(DatabaseConfig $databaseConfig): bool
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();

$this->validateParameter($username);
$this->validatePassword($password);

$createUser = ! $this->userExists($username);

if ($createUser) {
Expand All @@ -44,6 +47,8 @@ public function deleteUser(DatabaseConfig $databaseConfig): bool
// Tenant DB username
$username = $databaseConfig->getUsername();

$this->validateParameter($username);

// Tenant host connection config
$connectionName = $this->connection()->getConfig('name');
$centralDatabase = $this->connection()->getConfig('database');
Expand Down Expand Up @@ -77,6 +82,6 @@ public function deleteUser(DatabaseConfig $databaseConfig): bool

public function userExists(string $username): bool
{
return (bool) $this->connection()->selectOne("SELECT usename FROM pg_user WHERE usename = '{$username}'");
return (bool) $this->connection()->select('SELECT usename FROM pg_user WHERE usename = ?', [$username]);
}
}
92 changes: 92 additions & 0 deletions src/Database/Concerns/ValidatesDatabaseParameters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace Stancl\Tenancy\Database\Concerns;

use Illuminate\Support\Arr;
use InvalidArgumentException;

/**
* Provides methods to validate database parameters (e.g. database names, usernames, passwords)
* before using them in SQL statements (or in file paths in the case of SQLiteDatabaseManager).
*
* Used where parameters can be provided by users, and where parameter binding cannot be used.
*
* @see \Stancl\Tenancy\Database\TenantDatabaseManagers\TenantDatabaseManager
* @see \Stancl\Tenancy\Database\TenantDatabaseManagers\SQLiteDatabaseManager
*/
trait ValidatesDatabaseParameters
{
/**
* Characters allowed in parameters.
*
* Used as the default allowlist in validateParameter(), which validates non-password
* parameters such as database names or usernames.
*
* Since non-password parameters don't need to use as many special characters, we use
* a stricter allowlist here.
*/
public static string $allowedParameterCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';

/**
* Characters allowed in database user passwords.
*
* The allowlist for passwords is less strict than for other parameters
* because it's more common to use more special characters in passwords.
*/
public static string $allowedPasswordCharacters = ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~';

/**
* Ensure that parameters (database names, usernames, etc.)
* only contain allowed characters before used in SQL statements
* (or paths in the case of SQLiteDatabaseManager).
*
* By default, only the characters in allowedParameterCharacters() are allowed.
*
* @throws InvalidArgumentException
*/
protected function validateParameter(string|array|null $parameters, string|null $allowedCharacters = null): void
{
if ($parameters === null) {
throw new InvalidArgumentException('Parameter cannot be null.');
}

$allowedCharacters ??= static::$allowedParameterCharacters;

foreach (Arr::wrap($parameters) as $parameter) {
if (is_null($parameter)) {
throw new InvalidArgumentException('Parameter cannot be null.');
}

if (is_numeric($parameter)) {
$parameter = (string) $parameter;
}
Comment on lines +58 to +60

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you describe that case in a bit more detail?

I think null may be something worth thinking about more because you can end up with a bit weird queries. They'd be fine in most cases, I assume just "" values but worth considering when exactly this comes up and what are the exact implications.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume just "" values

You're right about that -- with MySQL, it'd create an "anonymous" user (empty string name), which sounds invalid, but the user creation would pass. Postgres would instead reject the null/empty string. So maybe actually worth considering throwing an exception here instead of just skipping?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah I was thinking if that could break anything but we selectively choose when to validate some parameter so all those places that call validateParameter(...) should already have established that the value is not null (or at least expect the value to not be null, which would make an exception appropriate here).

So for optional things like charset you'd only validate the param after checking that there is some charset set — it's not null — while for things like username/password those places simply expect the values to not be null, so an exception seems appropriate to validate that expectation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the logic accordingly 48b8aac


if (! is_string($parameter)) {
// E.g. if a parameter is retrieved from the config, it isn't necessarily a string
Comment thread
lukinovec marked this conversation as resolved.
throw new InvalidArgumentException('Parameter has to be a string.');
}

foreach (str_split($parameter) as $character) {
if (! str_contains($allowedCharacters, $character)) {
throw new InvalidArgumentException("Forbidden character '{$character}' in parameter.");
}
}
}
}

/**
* Ensure password only contains allowed characters (allowedPasswordCharacters())
* before being used in SQL statements.
*
* Used in permission controlled managers as a shorthand for calling validateParameter()
* with the less strict allowlist to validate database user passwords.
*
* @throws InvalidArgumentException
*/
protected function validatePassword(string|null $password): void
{
$this->validateParameter($password, allowedCharacters: static::$allowedPasswordCharacters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ public function createDatabase(TenantWithDatabase $tenant): bool
{
$database = $tenant->database()->getName();

$this->validateParameter($database);

return $this->connection()->statement("CREATE DATABASE [{$database}]");
}

public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP DATABASE [{$tenant->database()->getName()}]");
$database = $tenant->database()->getName();

$this->validateParameter($database);

return $this->connection()->statement("DROP DATABASE [{$database}]");
}

public function databaseExists(string $name): bool
{
return (bool) $this->connection()->select("SELECT name FROM master.sys.databases WHERE name = '$name'");
return (bool) $this->connection()->select('SELECT name FROM master.sys.databases WHERE name = ?', [$name]);
}
}
26 changes: 23 additions & 3 deletions src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,36 @@ public function createDatabase(TenantWithDatabase $tenant): bool
$charset = $this->connection()->getConfig('charset');
$collation = $this->connection()->getConfig('collation');

return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
$this->validateParameter(array_filter([$database, $charset, $collation], fn ($param) => $param !== null));

// MySQL defaults to the server's charset and collation
// if charset and collation are not specified.
// If charset is specified but collation is null, MySQL
// will choose a default collation for the specified charset (and vice versa).
$statement = "CREATE DATABASE `{$database}`";

if ($charset !== null) {
$statement .= " CHARACTER SET `{$charset}`";
}

if ($collation !== null) {
$statement .= " COLLATE `{$collation}`";
}

return $this->connection()->statement($statement);
}

public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
$database = $tenant->database()->getName();

$this->validateParameter($database);

return $this->connection()->statement("DROP DATABASE `{$database}`");
}

public function databaseExists(string $name): bool
{
return (bool) $this->connection()->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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public function createUser(DatabaseConfig $databaseConfig): bool
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();

$this->validateParameter([$database, $username]);
$this->validatePassword($password);

// Create login
$this->connection()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'");

Expand All @@ -37,12 +40,16 @@ public function createUser(DatabaseConfig $databaseConfig): bool

public function deleteUser(DatabaseConfig $databaseConfig): bool
{
return $this->connection()->statement("DROP LOGIN [{$databaseConfig->getUsername()}]");
$username = $databaseConfig->getUsername();

$this->validateParameter($username);

return $this->connection()->statement("DROP LOGIN [{$username}]");
}

public function userExists(string $username): bool
{
return (bool) $this->connection()->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
Expand All @@ -54,11 +61,15 @@ public function makeConnectionConfig(array $baseConfig, string $databaseName): a

public function deleteDatabase(TenantWithDatabase $tenant): bool
{
$name = $tenant->database()->getName();

$this->validateParameter($name);

// Close all connections to the database before deleting it
// Set the database to SINGLE_USER mode to ensure that
// No other connections are using the database while we're trying to delete it
// Rollback all active transactions
$this->connection()->statement("ALTER DATABASE [{$tenant->database()->getName()}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
$this->connection()->statement("ALTER DATABASE [{$name}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");

return parent::deleteDatabase($tenant);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public function createUser(DatabaseConfig $databaseConfig): bool
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();

$this->validateParameter([$database, $username]);
$this->validatePassword($password);

$this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");

$grants = implode(', ', static::$grants);
Expand All @@ -48,11 +51,15 @@ protected function isVersion8(): bool

public function deleteUser(DatabaseConfig $databaseConfig): bool
{
return $this->connection()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'");
$username = $databaseConfig->getUsername();

$this->validateParameter($username);

return $this->connection()->statement("DROP USER IF EXISTS '{$username}'");
}

public function userExists(string $username): bool
{
return (bool) $this->connection()->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(*)'};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ protected function grantPermissions(DatabaseConfig $databaseConfig): bool
$username = $databaseConfig->getUsername();
$schema = $databaseConfig->connection()['search_path'];

$this->validateParameter([$database, $username, $schema]);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Host config
$connectionName = $this->connection()->getConfig('name');
$centralDatabase = $this->connection()->getConfig('database');
Expand All @@ -32,10 +34,10 @@ protected function grantPermissions(DatabaseConfig $databaseConfig): bool
$this->connection()->reconnect();

// Grant permissions to create and use tables in the configured schema ("public" by default) to the user
$this->connection()->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
$this->connection()->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
config(["database.connections.{$connectionName}.database" => $centralDatabase]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,25 @@ protected function grantPermissions(DatabaseConfig $databaseConfig): bool
// Central database name
$database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName();

$this->connection()->statement("GRANT CONNECT ON DATABASE {$database} TO \"{$username}\"");
$this->validateParameter([$username, $schema, $database]);

$this->connection()->statement("GRANT CONNECT ON DATABASE \"{$database}\" TO \"{$username}\"");
$this->connection()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\"");
$this->connection()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\"");

$tables = $this->connection()->select("SELECT table_name FROM information_schema.tables WHERE table_schema = '{$schema}' AND table_type = 'BASE TABLE'");
$tables = $this->connection()->select("SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = 'BASE TABLE'", [$schema]);

// Grant permissions to any existing tables. This is used with RLS
foreach ($tables as $table) {
$tableName = $table->table_name;

/** @var string $primaryKey */
$primaryKey = $this->connection()->selectOne(<<<SQL
$primaryKey = $this->connection()->selectOne(<<<'SQL'
SELECT column_name
FROM information_schema.key_column_usage
WHERE table_name = '{$tableName}'
WHERE table_name = ?
AND constraint_name LIKE '%_pkey'
SQL)->column_name;
SQL, [$tableName])->column_name;
Comment thread
lukinovec marked this conversation as resolved.

Comment thread
lukinovec marked this conversation as resolved.
// Grant all permissions for all existing tables
$this->connection()->statement("GRANT ALL ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\"");
Expand Down
Loading
Loading