Skip to content
Open
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
8 changes: 8 additions & 0 deletions config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
'pooled' => env('DB_POOLED', false),
'direct' => array_filter([
'host' => env('DB_DIRECT_HOST'),
'port' => env('DB_DIRECT_PORT'),
'username' => env('DB_DIRECT_USERNAME'),
'password' => env('DB_DIRECT_PASSWORD'),
'sslmode' => env('DB_DIRECT_SSLMODE'),
]),
],

'sqlsrv' => [
Expand Down
100 changes: 94 additions & 6 deletions src/Illuminate/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,27 @@ class Connection implements ConnectionInterface
*/
protected $readPdo;

/**
* The active PDO connection used for direct connections.
*
* @var \PDO|(\Closure(): \PDO)
*/
protected $directPdo;

/**
* The database connection configuration options for reading.
*
* @var array
*/
protected $readPdoConfig = [];

/**
* The database connection configuration options for direct connections.
*
* @var array
*/
protected $directPdoConfig = [];

/**
* The name of the connected database.
*
Expand Down Expand Up @@ -213,7 +227,7 @@ class Connection implements ConnectionInterface
/**
* The last retrieved PDO read / write type.
*
* @var null|'read'|'write'
* @var null|'read'|'write'|'direct'
*/
protected $latestPdoTypeRetrieved = null;

Expand Down Expand Up @@ -1059,7 +1073,7 @@ public function reconnectIfMissingConnection()
*/
public function disconnect()
{
$this->setPdo(null)->setReadPdo(null);
$this->setPdo(null)->setReadPdo(null)->setDirectPdo(null);
}

/**
Expand Down Expand Up @@ -1327,6 +1341,32 @@ public function getRawReadPdo()
return $this->readPdo;
}

/**
* Get the current PDO connection used for direct connections.
*
* @return \PDO
*/
public function getDirectPdo()
{
$this->latestPdoTypeRetrieved = 'direct';

if ($this->directPdo instanceof Closure) {
return $this->directPdo = call_user_func($this->directPdo);
}

return $this->directPdo ?: $this->getPdo();
}

/**
* Get the current direct PDO connection parameter without executing any reconnect logic.
*
* @return \PDO|\Closure|null
*/
public function getRawDirectPdo()
{
return $this->directPdo;
}

/**
* Set the PDO connection.
*
Expand Down Expand Up @@ -1355,6 +1395,19 @@ public function setReadPdo($pdo)
return $this;
}

/**
* Set the PDO connection used for direct connections.
*
* @param \PDO|\Closure|null $pdo
* @return $this
*/
public function setDirectPdo($pdo)
{
$this->directPdo = $pdo;

return $this;
}

/**
* Set the read PDO connection configuration.
*
Expand All @@ -1368,6 +1421,39 @@ public function setReadPdoConfig(array $config)
return $this;
}

/**
* Set the direct PDO connection configuration.
*
* @param array $config
* @return $this
*/
public function setDirectPdoConfig(array $config)
{
$this->directPdoConfig = $config;

return $this;
}

/**
* Get the direct PDO connection configuration.
*
* @return array
*/
public function getDirectConfig()
{
return $this->directPdoConfig;
}

/**
* Determine if this connection has a direct PDO connection configured.
*
* @return bool
*/
public function usesDirectConnection()
{
return ! empty($this->directPdoConfig);
}

/**
* Set the reconnect instance on the connection.
*
Expand Down Expand Up @@ -1421,9 +1507,11 @@ public function getConfig($option = null)
*/
protected function getConnectionDetails()
{
$config = $this->latestReadWriteTypeUsed() === 'read'
? $this->readPdoConfig
: $this->config;
$config = match ($this->latestReadWriteTypeUsed()) {
'read' => $this->readPdoConfig,
'direct' => $this->directPdoConfig,
default => $this->config,
};

return [
'driver' => $this->getDriverName(),
Expand Down Expand Up @@ -1705,7 +1793,7 @@ public function setReadWriteType($readWriteType)
/**
* Retrieve the latest read / write type used.
*
* @return 'read'|'write'|null
* @return 'read'|'write'|'direct'|null
*/
protected function latestReadWriteTypeUsed()
{
Expand Down
152 changes: 150 additions & 2 deletions src/Illuminate/Database/Connectors/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Database\SqlServerConnection;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use PDO;
use PDOException;

class ConnectionFactory
Expand Down Expand Up @@ -42,6 +43,7 @@ public function __construct(Container $container)
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
$config = $this->applyPooledPostgresOptions($config);

if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
Expand Down Expand Up @@ -72,9 +74,16 @@ protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);

return $this->createConnection(
$connection = $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);

if ($this->hasDirectConnection($config)) {
$connection->setDirectPdo($this->createDirectPdo($config))
->setDirectPdoConfig($this->getDirectConfig($config));
}

return $connection;
}

/**
Expand All @@ -87,9 +96,16 @@ protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));

return $connection
$connection
->setReadPdo($this->createReadPdo($config))
->setReadPdoConfig($this->getReadConfig($config));

if ($this->hasDirectConnection($config)) {
$connection->setDirectPdo($this->createDirectPdo($config))
->setDirectPdoConfig($this->getDirectConfig($config));
}

return $connection;
}

/**
Expand Down Expand Up @@ -129,6 +145,30 @@ protected function getWriteConfig(array $config)
);
}

/**
* Create a new PDO instance for direct connections.
*
* @param array $config
* @return \Closure
*/
protected function createDirectPdo(array $config)
{
return $this->createPdoResolver($this->getDirectConfig($config));
}

/**
* Get the direct configuration for a connection.
*
* @param array $config
* @return array
*/
protected function getDirectConfig(array $config)
{
return $this->mergeDirectConfig(
$config, $this->getReadWriteConfig($config, 'direct')
);
}

/**
* Get a read / write level configuration.
*
Expand All @@ -155,6 +195,114 @@ protected function mergeReadWriteConfig(array $config, array $merge)
return Arr::except(array_merge($config, $merge), ['read', 'write']);
}

/**
* Merge a configuration for a direct connection.
*
* @param array $config
* @param array $merge
* @return array
*/
protected function mergeDirectConfig(array $config, array $merge)
{
$direct = Arr::except(array_merge($config, $merge), [
'read', 'write', 'direct', 'pooled', 'connect_via_database', 'connect_via_port',
]);

if (! isset($direct['options']) || ! is_array($direct['options'])) {
$direct['options'] = [];
}

$directEmulatePreparesConfigured = isset($merge['options']) &&
is_array($merge['options']) &&
array_key_exists(PDO::ATTR_EMULATE_PREPARES, $merge['options']);

if (! $directEmulatePreparesConfigured) {
$direct['options'][PDO::ATTR_EMULATE_PREPARES] = false;
}

return $direct;
}

/**
* Apply transaction-pooler options to PostgreSQL connections.
*
* @param array $config
* @return array
*/
protected function applyPooledPostgresOptions(array $config)
{
if (($config['driver'] ?? null) !== 'pgsql') {
return $config;
}

$hasDirectConnection = ! empty($config['direct']);

if (! $hasDirectConnection && ($config['pooled'] ?? false) !== true) {
return $config;
}

if ($hasDirectConnection) {
$config['pooled'] = true;
}

if (! $hasDirectConnection && ($config['pooled'] ?? false) === true) {
trigger_error(
"Database connection [{$config['name']}] sets 'pooled' => true without a 'direct' endpoint; migrations and DDL will still traverse the transaction pooler.",
E_USER_WARNING
);
}

$config = $this->withEmulatedPrepares($config);

foreach (['read', 'write'] as $type) {
if (! isset($config[$type])) {
continue;
}

if (isset($config[$type][0])) {
foreach ($config[$type] as $index => $connection) {
if (isset($connection['options'])) {
$config[$type][$index] = $this->withEmulatedPrepares($connection);
}
}
} elseif (isset($config[$type]['options'])) {
$config[$type] = $this->withEmulatedPrepares($config[$type]);
}
}

return $config;
}

/**
* Stamp emulated prepares onto a connection configuration when not explicit.
*
* @param array $config
* @return array
*/
protected function withEmulatedPrepares(array $config)
{
if (! isset($config['options']) || ! is_array($config['options'])) {
$config['options'] = [];
}

if (! array_key_exists(PDO::ATTR_EMULATE_PREPARES, $config['options'] ?? [])) {
$config['options'][PDO::ATTR_EMULATE_PREPARES] = true;
}

return $config;
}

/**
* Determine if the configuration has a direct PostgreSQL connection.
*
* @param array $config
* @return bool
*/
protected function hasDirectConnection(array $config)
{
return ($config['driver'] ?? null) === 'pgsql' && ! empty($config['direct']);
}

/**
* Create a new Closure that resolves to a PDO instance.
*
Expand Down
Loading