Verified Commit 9b33c68d authored by Dave Long's avatar Dave Long
Browse files

perf: #3615187 Set names to UTF8 takes an extra round trip to the database

By: daffie
By: smustgrave
parent 1ef5fe73
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -111,9 +111,6 @@ public function __construct(Pgsql $connection, array $connection_options) {

    parent::__construct($connection, $connection_options);

    // Force PostgreSQL to use the UTF-8 character set by default.
    $this->connection->exec("SET NAMES 'UTF8'");

    // Execute PostgreSQL init_commands.
    if (isset($connection_options['init_commands'])) {
      $this->connection->exec(implode('; ', $connection_options['init_commands']));
@@ -165,7 +162,8 @@ public static function open(#[\SensitiveParameter] array &$connection_options =
    }

    $connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1');
    $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
    // Force PostgreSQL to use the UTF-8 character set by default.
    $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'] . ' client_encoding=UTF8';

    // Allow PDO options to be overridden.
    $connection_options += [
+50 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\pgsql\Kernel\pgsql;

use Drupal\Core\Database\Database;
use Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

// cspell:ignore héllo wörld PGCLIENTENCODING libpq

/**
 * PostgreSQL-specific connection tests.
 */
#[Group('Database')]
#[RunTestsInSeparateProcesses]
class ConnectionTest extends DriverSpecificKernelTestBase {

  /**
   * Tests the client encoding is set to UTF8 when the connection is opened.
   *
   * The client encoding is set via the DSN in
   * \Drupal\pgsql\Driver\Database\pgsql\Connection::open(), so it is part of
   * the connection startup packet and no "SET NAMES" query is needed.
   */
  public function testClientEncoding(): void {
    $this->assertSame('UTF8', $this->connection->query('SHOW client_encoding')->fetchField());

    // Ensure multi-byte UTF-8 data round trips intact.
    $string = 'héllo wörld ✓ 漢字';
    $this->assertSame($string, $this->connection->query('SELECT :string::text', [':string' => $string])->fetchField());

    // The client encoding in the DSN must take precedence over the
    // PGCLIENTENCODING environment variable, which libpq would otherwise use
    // as the default.
    putenv('PGCLIENTENCODING=LATIN1');
    try {
      Database::addConnectionInfo('encoding_test', 'default', Database::getConnectionInfo()['default']);
      $connection = Database::getConnection('default', 'encoding_test');
      $this->assertSame('UTF8', $connection->query('SHOW client_encoding')->fetchField());
    }
    finally {
      putenv('PGCLIENTENCODING');
      Database::closeConnection('default', 'encoding_test');
    }
  }

}
+52 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\pgsql\Unit;

use Drupal\pgsql\Driver\Database\pgsql\Connection;
use Drupal\Tests\UnitTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use Pdo\Pgsql;

/**
 * Tests Drupal\pgsql\Driver\Database\pgsql\Connection.
 */
#[CoversClass(Connection::class)]
#[Group('Database')]
class ConnectionTest extends UnitTestCase {

  /**
   * Tests that no queries are executed when opening a connection.
   *
   * The client encoding is set through the DSN in ::open(), as part of the
   * connection startup packet, so constructing the connection must not send
   * any statement (an extra round trip) to the database.
   */
  public function testNoQueriesOnConnect(): void {
    $pdo = $this->createMock(Pgsql::class);
    $pdo->expects($this->never())->method('exec');
    $pdo->expects($this->never())->method('query');
    $pdo->expects($this->never())->method('prepare');

    new Connection($pdo, []);
  }

  /**
   * Tests that init_commands are executed when opening a connection.
   */
  public function testInitCommandsOnConnect(): void {
    $pdo = $this->createMock(Pgsql::class);
    $pdo->expects($this->once())
      ->method('exec')
      ->with("SET timezone = 'UTC'");

    new Connection($pdo, [
      'init_commands' => [
        "SET timezone = 'UTC'",
      ],
    ]);
  }

}