1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-05 04:34:03 +00:00

Simplify and clarify comment-related TableRLSManager code

This commit is contained in:
lukinovec 2025-05-21 13:20:18 +02:00
parent f65c64c9c7
commit 349ac6e9fc
2 changed files with 19 additions and 29 deletions

View file

@ -82,9 +82,9 @@ class TableRLSManager implements RLSPolicyManager
$table = str($table)->afterLast('.')->toString(); $table = str($table)->afterLast('.')->toString();
// For each table, we get a list of all foreign key columns // For each table, we get a list of all foreign key columns
$foreignKeys = collect($builder->getForeignKeys($table))->merge($this->getCommentConstraints($table))->map(function ($foreign) use ($table) { $foreignKeys = collect($builder->getForeignKeys($table))
return $this->formatForeignKey($foreign, $table); ->merge($this->getCommentConstraints($table))
}); ->map(fn ($foreign) => $this->formatForeignKey($foreign, $table));
// We loop through each foreign key column and find // We loop through each foreign key column and find
// all possible paths that lead to the tenants table // all possible paths that lead to the tenants table
@ -149,56 +149,48 @@ class TableRLSManager implements RLSPolicyManager
} }
/** /**
* Retrieve comment-based constraints for a table. These are columns with comments in the format: * Retrieve table's comment-based constraints. These are columns with comments
* "rls <foreign_table>.<foreign_column>" * formatted like "rls <foreign_table>.<foreign_column>".
* *
* Returns the constraints as unformatted foreign key arrays, ready to be passed to $this->formatForeignKey().
* *
* Throws an exception if the comment is formatted incorrectly or if the referenced table/column does not exist. * Throws an exception if the comment is formatted incorrectly or if the referenced table/column does not exist.
*/ */
protected function getCommentConstraints(string $tableName): array protected function getCommentConstraints(string $tableName): array
{ {
$columns = $this->database->getSchemaBuilder()->getColumns($tableName); $builder = $this->database->getSchemaBuilder();
$schemaBuilder = $this->database->getSchemaBuilder(); $commentConstraintColumns = array_filter($builder->getColumns($tableName), function ($column) {
$commentConstraints = array_filter($columns, function ($column) {
return (isset($column['comment']) && is_string($column['comment'])) return (isset($column['comment']) && is_string($column['comment']))
&& Str::startsWith($column['comment'], 'rls '); && Str::startsWith($column['comment'], 'rls ');
}); });
return array_map(function ($commentConstraint) use ($schemaBuilder, $tableName) { return array_map(function ($column) use ($builder, $tableName) {
$comment = $commentConstraint['comment']; $constraint = explode('.', Str::after($column['comment'], 'rls '));
$constraintString = Str::after($comment, 'rls ');
$constraint = explode('.', $constraintString);
// Validate comment constraint format // Validate comment constraint format
if (count($constraint) !== 2 || empty($constraint[0]) || empty($constraint[1])) { if (count($constraint) !== 2 || empty($constraint[0]) || empty($constraint[1])) {
throw new RLSCommentConstraintException("Incorrectly formatted comment constraint on {$tableName}.{$commentConstraint['name']}: '{$comment}'"); throw new RLSCommentConstraintException("Incorrectly formatted comment constraint on {$tableName}.{$column['name']}: '{$column['comment']}'");
} }
$foreignTable = $constraint[0]; $foreignTable = $constraint[0];
$foreignColumn = $constraint[1]; $foreignColumn = $constraint[1];
// Validate table existence // Validate table existence
$allTables = array_map(function ($table) { if (! $builder->hasTable($foreignTable)) {
return str($table)->afterLast('.')->toString(); throw new RLSCommentConstraintException("Comment constraint on {$tableName}.{$column['name']} references non-existent table '{$foreignTable}'");
}, $schemaBuilder->getTableListing(schema: $this->database->getConfig('search_path')));
if (! in_array($foreignTable, $allTables, true)) {
throw new RLSCommentConstraintException("Comment constraint on {$tableName}.{$commentConstraint['name']} references non-existent table '{$foreignTable}'");
} }
// Validate column existence // Validate column existence
$foreignColumns = $schemaBuilder->getColumns($foreignTable); if (! $builder->hasColumn($foreignTable, $foreignColumn)) {
$foreignColumnNames = array_column($foreignColumns, 'name'); throw new RLSCommentConstraintException("Comment constraint on {$tableName}.{$column['name']} references non-existent column '{$foreignTable}.{$foreignColumn}'");
if (! in_array($foreignColumn, $foreignColumnNames, true)) {
throw new RLSCommentConstraintException("Comment constraint on {$tableName}.{$commentConstraint['name']} references non-existent column '{$foreignTable}.{$foreignColumn}'");
} }
return [ return [
'foreign_table' => $foreignTable, 'foreign_table' => $foreignTable,
'foreign_columns' => [$foreignColumn], 'foreign_columns' => [$foreignColumn],
'columns' => [$commentConstraint['name']], 'columns' => [$column['name']],
]; ];
}, $commentConstraints); }, $commentConstraintColumns);
} }
/** Get tree's non-nullable paths. */ /** Get tree's non-nullable paths. */
@ -266,7 +258,7 @@ class TableRLSManager implements RLSPolicyManager
*/ */
protected function formatForeignKey(array $foreignKey, string $table): array protected function formatForeignKey(array $foreignKey, string $table): array
{ {
// $foreignKey is one of the foreign keys retrieved by $this->database->getSchemaBuilder()->getForeignKeys($table) // $foreignKey is an unformatted foreign key retrieved by $this->database->getSchemaBuilder()->getForeignKeys($table)
return [ return [
'foreignKey' => $foreignKeyName = $foreignKey['columns'][0], 'foreignKey' => $foreignKeyName = $foreignKey['columns'][0],
'foreignTable' => $foreignKey['foreign_table'], 'foreignTable' => $foreignKey['foreign_table'],

View file

@ -703,8 +703,6 @@ test('table manager can generate paths leading through non-constrained foreign k
], ],
], ],
'non_constrained_users' => [ 'non_constrained_users' => [
// Category tree gets excluded because the category table is related to the tenant table
// only through a column with the 'no-rls' comment
'tenant_id' => [ 'tenant_id' => [
[ [
[ [