From 8e8642462a356a82967634c5631b66183adb7aee Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Fri, 11 Dec 2020 14:36:02 -0500 Subject: [PATCH 1/3] Fix issue with special $user search path variable not being resolved For any scenario in which PostgreSQL's internal "information_schema" is queried using a schema/schemas derived from the search_path configured on the connection, it is necessary to resolve PostgreSQL's special "$user" variable to the actual username set on the connection. While this resolution is performed correctly in the related parseSchemaAndTable() method, it was not being performed here, prior to this change. --- src/Illuminate/Database/Schema/PostgresBuilder.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 0cba5f4d9a16..c249ccf02ebe 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -107,7 +107,9 @@ public function dropAllTypes() public function getAllTables() { return $this->connection->select( - $this->grammar->compileGetAllTables((array) $this->connection->getConfig('schema')) + $this->grammar->compileGetAllTables( + $this->parseSearchPath($this->connection->getConfig('search_path')) + ) ); } @@ -119,7 +121,9 @@ public function getAllTables() public function getAllViews() { return $this->connection->select( - $this->grammar->compileGetAllViews((array) $this->connection->getConfig('schema')) + $this->grammar->compileGetAllViews( + $this->parseSearchPath($this->connection->getConfig('search_path')) + ) ); } @@ -209,6 +213,10 @@ protected function parseSearchPath($searchPath) array_walk($searchPath, function (&$schema) { $schema = trim($schema, '\'"'); + + $schema = $schema === '$user' + ? $this->connection->getConfig('username') + : $schema; }); return $searchPath; From af39bcdaa6505ef2587e839b0dc88490010e614f Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Sat, 12 Dec 2020 15:59:01 -0500 Subject: [PATCH 2/3] Update variable name to reflect what it actually represents --- .../Database/Schema/Grammars/PostgresGrammar.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index f6450c1b41c9..8486e499d313 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -246,12 +246,12 @@ public function compileDropAllTypes($types) /** * Compile the SQL needed to retrieve all table names. * - * @param string|array $schema + * @param string|array $searchPath * @return string */ - public function compileGetAllTables($schema) + public function compileGetAllTables($searchPath) { - return "select tablename from pg_catalog.pg_tables where schemaname in ('".implode("','", (array) $schema)."')"; + return "select tablename from pg_catalog.pg_tables where schemaname in ('".implode("','", (array) $searchPath)."')"; } /** @@ -260,9 +260,9 @@ public function compileGetAllTables($schema) * @param string|array $schema * @return string */ - public function compileGetAllViews($schema) + public function compileGetAllViews($searchPath) { - return "select viewname from pg_catalog.pg_views where schemaname in ('".implode("','", (array) $schema)."')"; + return "select viewname from pg_catalog.pg_views where schemaname in ('".implode("','", (array) $searchPath)."')"; } /** From 235334e0ca69c0c20e7ef16ea8a0196b4c54d91f Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Sat, 12 Dec 2020 16:30:11 -0500 Subject: [PATCH 3/3] Add new tests to cover search_path behavior when dropping all tables --- .../Database/DatabasePostgresBuilderTest.php | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Database/DatabasePostgresBuilderTest.php b/tests/Database/DatabasePostgresBuilderTest.php index f41e271a4428..54e64e2e54e5 100644 --- a/tests/Database/DatabasePostgresBuilderTest.php +++ b/tests/Database/DatabasePostgresBuilderTest.php @@ -241,6 +241,51 @@ public function testWhenDatabaseNotDefaultGetColumnListingWithFullyQualifiedRefe $builder->getColumnListing('mydatabase.myapp.foo'); } + /** + * Ensure that when the search_path contains just one schema, only that + * schema is passed into the query that is executed to acquire the list + * of tables to be dropped. + */ + public function testDropAllTablesWithOneSchemaInSearchPath() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); + $connection->shouldReceive('getConfig')->with('dont_drop')->andReturn(['foo']); + $grammar = m::mock(PostgresGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileGetAllTables')->with(['public'])->andReturn("select tablename from pg_catalog.pg_tables where schemaname in ('public')"); + $connection->shouldReceive('select')->with("select tablename from pg_catalog.pg_tables where schemaname in ('public')")->andReturn(['users']); + $grammar->shouldReceive('compileDropAllTables')->with(['users'])->andReturn('drop table "'.implode('","', ['users']).'" cascade'); + $connection->shouldReceive('statement')->with('drop table "'.implode('","', ['users']).'" cascade'); + $builder = $this->getBuilder($connection); + + $builder->dropAllTables(); + } + + /** + * Ensure that when the search_path contains more than one schema, both + * schemas are passed into the query that is executed to acquire the list + * of tables to be dropped. Furthermore, ensure that the special '$user' + * variable is resolved to the username specified on the database connection + * in the process. + */ + public function testDropAllTablesWithMoreThanOneSchemaInSearchPath() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('username')->andReturn('foouser'); + $connection->shouldReceive('getConfig')->with('search_path')->andReturn('"$user", public'); + $connection->shouldReceive('getConfig')->with('dont_drop')->andReturn(['foo']); + $grammar = m::mock(PostgresGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileGetAllTables')->with(['foouser', 'public'])->andReturn("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public')"); + $connection->shouldReceive('select')->with("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public')")->andReturn(['users', 'users']); + $grammar->shouldReceive('compileDropAllTables')->with(['users', 'users'])->andReturn('drop table "'.implode('","', ['users', 'users']).'" cascade'); + $connection->shouldReceive('statement')->with('drop table "'.implode('","', ['users', 'users']).'" cascade'); + $builder = $this->getBuilder($connection); + + $builder->dropAllTables(); + } + protected function getConnection() { return m::mock(Connection::class);