Skip to content

Commit

Permalink
Merge branch '10.x' into 11.x
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	src/Illuminate/Foundation/Application.php
  • Loading branch information
driesvints committed Jul 9, 2024
2 parents 85eb158 + e052cb8 commit b523eca
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 54 deletions.
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
8 changes: 6 additions & 2 deletions src/Illuminate/Http/Client/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,15 @@ public function send(string $method, string $url, array $options = [])
$response->throw($this->throwCallback);
}

if ($attempt < $this->tries && $shouldRetry) {
$potentialTries = is_array($this->tries)
? count($this->tries) + 1
: $this->tries;

if ($attempt < $potentialTries && $shouldRetry) {
$response->throw();
}

if ($this->tries > 1 && $this->retryThrow) {
if ($potentialTries > 1 && $this->retryThrow) {
$response->throw();
}
}
Expand Down
23 changes: 10 additions & 13 deletions tests/Database/DatabaseConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,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 @@ -92,16 +92,15 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments()
#[DataProvider('provideSearchPaths')]
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 @@ -179,33 +178,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
139 changes: 139 additions & 0 deletions tests/Http/HttpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,28 @@ public function testRequestExceptionIsThrownWhenRetriesExhausted()
$this->factory->assertSentCount(2);
}

public function testRequestExceptionIsThrownWhenRetriesExhaustedWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->response(['error'], 403),
]);

$exception = null;

try {
$this->factory
->retry([1], 0, null, true)
->get('http://foo.com/get');
} catch (RequestException $e) {
$exception = $e;
}

$this->assertNotNull($exception);
$this->assertInstanceOf(RequestException::class, $exception);

$this->factory->assertSentCount(2);
}

public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessary()
{
$this->factory->fake([
Expand Down Expand Up @@ -1754,6 +1776,35 @@ public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessary()
$this->factory->assertSentCount(1);
}

public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessaryWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->response(['error'], 500),
]);

$exception = null;
$whenAttempts = 0;

try {
$this->factory
->retry([1000, 1000], 1000, function ($exception) use (&$whenAttempts) {
$whenAttempts++;

return $exception->response->status() === 403;
}, true)
->get('http://foo.com/get');
} catch (RequestException $e) {
$exception = $e;
}

$this->assertNotNull($exception);
$this->assertInstanceOf(RequestException::class, $exception);

$this->assertSame(1, $whenAttempts);

$this->factory->assertSentCount(1);
}

public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhausted()
{
$this->factory->fake([
Expand All @@ -1769,6 +1820,21 @@ public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhausted()
$this->factory->assertSentCount(2);
}

public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhaustedWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->response(['error'], 403),
]);

$response = $this->factory
->retry([1, 2], throw: false)
->get('http://foo.com/get');

$this->assertTrue($response->failed());

$this->factory->assertSentCount(3);
}

public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessary()
{
$this->factory->fake([
Expand All @@ -1792,6 +1858,29 @@ public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessary
$this->factory->assertSentCount(1);
}

public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessaryWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->response(['error'], 500),
]);

$whenAttempts = 0;

$response = $this->factory
->retry([1, 2], 0, function ($exception) use (&$whenAttempts) {
$whenAttempts++;

return $exception->response->status() === 403;
}, false)
->get('http://foo.com/get');

$this->assertTrue($response->failed());

$this->assertSame(1, $whenAttempts);

$this->factory->assertSentCount(1);
}

public function testRequestCanBeModifiedInRetryCallback()
{
$this->factory->fake([
Expand All @@ -1817,6 +1906,31 @@ public function testRequestCanBeModifiedInRetryCallback()
});
}

public function testRequestCanBeModifiedInRetryCallbackWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->sequence()
->push(['error'], 500)
->push(['ok'], 200),
]);

$response = $this->factory
->retry([2], when: function ($exception, $request) {
$this->assertInstanceOf(PendingRequest::class, $request);

$request->withHeaders(['Foo' => 'Bar']);

return true;
}, throw: false)
->get('http://foo.com/get');

$this->assertTrue($response->successful());

$this->factory->assertSent(function (Request $request) {
return $request->hasHeader('Foo') && $request->header('Foo') === ['Bar'];
});
}

public function testExceptionThrownInRetryCallbackWithoutRetrying()
{
$this->factory->fake([
Expand All @@ -1842,6 +1956,31 @@ public function testExceptionThrownInRetryCallbackWithoutRetrying()
$this->factory->assertSentCount(1);
}

public function testExceptionThrownInRetryCallbackWithoutRetryingWithBackoffArray()
{
$this->factory->fake([
'*' => $this->factory->response(['error'], 500),
]);

$exception = null;

try {
$this->factory
->retry([1, 2, 3], when: function ($exception) use (&$whenAttempts) {
throw new Exception('Foo bar');
}, throw: false)
->get('http://foo.com/get');
} catch (Exception $e) {
$exception = $e;
}

$this->assertNotNull($exception);
$this->assertInstanceOf(Exception::class, $exception);
$this->assertEquals('Foo bar', $exception->getMessage());

$this->factory->assertSentCount(1);
}

public function testRequestsWillBeWaitingSleepMillisecondsReceivedBeforeRetry()
{
Sleep::fake();
Expand Down

0 comments on commit b523eca

Please sign in to comment.