Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Set application_name and character set as PostgreSQL DSN string #51985

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 11 additions & 39 deletions src/Illuminate/Database/Connectors/PostgresConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,13 @@ public function connect(array $config)

$this->configureIsolationLevel($connection, $config);

$this->configureEncoding($connection, $config);

// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);

$this->configureSearchPath($connection, $config);

// Postgres allows an application_name to be set by the user and this name is
// used to when monitoring the application with pg_stat_activity. So we'll
// determine if the option has been specified and run a statement if so.
$this->configureApplicationName($connection, $config);

$this->configureSynchronousCommit($connection, $config);

return $connection;
Expand All @@ -71,22 +64,6 @@ protected function configureIsolationLevel($connection, array $config)
}
}

/**
* Set the connection character set and collation.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureEncoding($connection, $config)
{
if (! isset($config['charset'])) {
return;
}

$connection->prepare("set names '{$config['charset']}'")->execute();
}

/**
* Set the timezone on the connection.
*
Expand Down Expand Up @@ -132,22 +109,6 @@ protected function quoteSearchPath($searchPath)
return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"';
}

/**
* Set the application name on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureApplicationName($connection, $config)
{
if (isset($config['application_name'])) {
$applicationName = $config['application_name'];

$connection->prepare("set application_name to '$applicationName'")->execute();
}
}

/**
* Create a DSN string from a configuration.
*
Expand Down Expand Up @@ -178,6 +139,17 @@ protected function getDsn(array $config)
$dsn .= ";port={$port}";
}

if (isset($charset)) {
$dsn .= ";client_encoding='{$charset}'";
}

// Postgres allows an application_name to be set by the user and this name is
// used to when monitoring the application with pg_stat_activity. So we'll
// determine if the option has been specified and run a statement if so.
if (isset($application_name)) {
$dsn .= ";application_name='".str_replace("'", "\'", $application_name)."'";
}

return $this->addSslOptions($dsn, $config);
}

Expand Down
23 changes: 10 additions & 13 deletions tests/Database/DatabaseConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ public function testMySqlConnectCallsCreateConnectionWithIsolationLevel()

public function testPostgresConnectCallsCreateConnectionWithProperArguments()
{
$dsn = 'pgsql:host=foo;dbname=\'bar\';port=111';
$dsn = 'pgsql:host=foo;dbname=\'bar\';port=111;client_encoding=\'utf8\'';
$config = ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'charset' => 'utf8'];
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
$connection = m::mock(stdClass::class);
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
$statement = m::mock(PDOStatement::class);
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
$statement->shouldReceive('execute')->once();
$connection->shouldReceive('prepare')->zeroOrMoreTimes()->andReturn($statement);
$statement->shouldReceive('execute')->zeroOrMoreTimes();
$result = $connector->connect($config);

$this->assertSame($result, $connection);
Expand All @@ -97,16 +97,15 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments()
*/
public function testPostgresSearchPathIsSet($searchPath, $expectedSql)
{
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
$dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\'';
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => $searchPath, 'charset' => 'utf8'];
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
$connection = m::mock(stdClass::class);
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
$statement = m::mock(PDOStatement::class);
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
$connection->shouldReceive('prepare')->once()->with($expectedSql)->andReturn($statement);
$statement->shouldReceive('execute')->twice();
$statement->shouldReceive('execute')->once();
$result = $connector->connect($config);

$this->assertSame($result, $connection);
Expand Down Expand Up @@ -184,33 +183,31 @@ public static function provideSearchPaths()

public function testPostgresSearchPathFallbackToConfigKeySchema()
{
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
$dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\'';
$config = ['host' => 'foo', 'database' => 'bar', 'schema' => ['public', '"user"'], 'charset' => 'utf8'];
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
$connection = m::mock(stdClass::class);
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
$statement = m::mock(PDOStatement::class);
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
$connection->shouldReceive('prepare')->once()->with('set search_path to "public", "user"')->andReturn($statement);
$statement->shouldReceive('execute')->twice();
$statement->shouldReceive('execute')->once();
$result = $connector->connect($config);

$this->assertSame($result, $connection);
}

public function testPostgresApplicationNameIsSet()
{
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
$dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\';application_name=\'Laravel App\'';
$config = ['host' => 'foo', 'database' => 'bar', 'charset' => 'utf8', 'application_name' => 'Laravel App'];
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
$connection = m::mock(stdClass::class);
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
$statement = m::mock(PDOStatement::class);
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
$connection->shouldReceive('prepare')->once()->with('set application_name to \'Laravel App\'')->andReturn($statement);
$statement->shouldReceive('execute')->twice();
$connection->shouldReceive('prepare')->zeroOrMoreTimes()->andReturn($statement);
$statement->shouldReceive('execute')->zeroOrMoreTimes();
$result = $connector->connect($config);

$this->assertSame($result, $connection);
Expand Down