Browse Source

Merge pull request #16355 from cakephp/5.0-db-connection

Trim down public API of Connection.
ADmad 4 years ago
parent
commit
829fd47972

+ 2 - 177
src/Database/Connection.php

@@ -23,15 +23,12 @@ use Cake\Database\Exception\MissingConnectionException;
 use Cake\Database\Exception\MissingDriverException;
 use Cake\Database\Exception\MissingExtensionException;
 use Cake\Database\Exception\NestedTransactionRollbackException;
-use Cake\Database\Log\LoggedQuery;
-use Cake\Database\Log\QueryLogger;
 use Cake\Database\Retry\ReconnectStrategy;
 use Cake\Database\Schema\CachedCollection;
 use Cake\Database\Schema\Collection as SchemaCollection;
 use Cake\Database\Schema\CollectionInterface as SchemaCollectionInterface;
 use Cake\Datasource\ConnectionInterface;
 use Cake\Log\Log;
-use Psr\Log\LoggerInterface;
 use Psr\SimpleCache\CacheInterface;
 use RuntimeException;
 use Throwable;
@@ -81,20 +78,6 @@ class Connection implements ConnectionInterface
     protected bool $_useSavePoints = false;
 
     /**
-     * Whether to log queries generated during this connection.
-     *
-     * @var bool
-     */
-    protected bool $_logQueries = false;
-
-    /**
-     * Logger object instance.
-     *
-     * @var \Psr\Log\LoggerInterface|null
-     */
-    protected ?LoggerInterface $_logger = null;
-
-    /**
      * Cacher object instance.
      *
      * @var \Psr\SimpleCache\CacheInterface|null
@@ -137,15 +120,10 @@ class Connection implements ConnectionInterface
         $driverConfig = array_diff_key($config, array_flip([
             'name',
             'driver',
-            'log',
             'cacheMetaData',
             'cacheKeyPrefix',
         ]));
         $this->_driver = $this->createDriver($config['driver'] ?? '', $driverConfig);
-
-        if (!empty($config['log'])) {
-            $this->enableQueryLogging((bool)$config['log']);
-        }
     }
 
     /**
@@ -270,24 +248,6 @@ class Connection implements ConnectionInterface
     }
 
     /**
-     * Prepares a SQL statement to be executed.
-     *
-     * @param \Cake\Database\Query|string $query The SQL to convert into a prepared statement.
-     * @return \Cake\Database\StatementInterface
-     */
-    public function prepare(Query|string $query): StatementInterface
-    {
-        return $this->getDisconnectRetry()->run(function () use ($query) {
-            $statement = $this->_driver->prepare($query);
-            if ($this->_logQueries) {
-                $statement->setLogger($this->getLogger());
-            }
-
-            return $statement;
-        });
-    }
-
-    /**
      * Executes a query using $params for interpolating values and $types as a hint for each
      * those params.
      *
@@ -298,28 +258,7 @@ class Connection implements ConnectionInterface
      */
     public function execute(string $sql, array $params = [], array $types = []): StatementInterface
     {
-        return $this->getDisconnectRetry()->run(function () use ($sql, $params, $types) {
-            $statement = $this->prepare($sql);
-            if (!empty($params)) {
-                $statement->bind($params, $types);
-            }
-            $statement->execute();
-
-            return $statement;
-        });
-    }
-
-    /**
-     * Compiles a Query object into a SQL string according to the dialect for this
-     * connection's driver
-     *
-     * @param \Cake\Database\Query $query The query to be compiled
-     * @param \Cake\Database\ValueBinder $binder Value binder
-     * @return string
-     */
-    public function compileQuery(Query $query, ValueBinder $binder): string
-    {
-        return $this->getDriver()->compileQuery($query, $binder)[1];
+        return $this->getDisconnectRetry()->run(fn () => $this->_driver->execute($sql, $params, $types));
     }
 
     /**
@@ -331,29 +270,7 @@ class Connection implements ConnectionInterface
      */
     public function run(Query $query): StatementInterface
     {
-        return $this->getDisconnectRetry()->run(function () use ($query) {
-            $statement = $this->prepare($query);
-            $query->getValueBinder()->attachTo($statement);
-            $statement->execute();
-
-            return $statement;
-        });
-    }
-
-    /**
-     * Executes a SQL statement and returns the Statement object as result.
-     *
-     * @param string $sql The SQL query to execute.
-     * @return \Cake\Database\StatementInterface
-     */
-    public function query(string $sql): StatementInterface
-    {
-        return $this->getDisconnectRetry()->run(function () use ($sql) {
-            $statement = $this->prepare($sql);
-            $statement->execute();
-
-            return $statement;
-        });
+        return $this->getDisconnectRetry()->run(fn () => $this->_driver->run($query));
     }
 
     /**
@@ -465,10 +382,6 @@ class Connection implements ConnectionInterface
     public function begin(): void
     {
         if (!$this->_transactionStarted) {
-            if ($this->_logQueries) {
-                $this->log('BEGIN');
-            }
-
             $this->getDisconnectRetry()->run(function (): void {
                 $this->_driver->beginTransaction();
             });
@@ -507,9 +420,6 @@ class Connection implements ConnectionInterface
 
             $this->_transactionStarted = false;
             $this->nestedTransactionRollbackException = null;
-            if ($this->_logQueries) {
-                $this->log('COMMIT');
-            }
 
             return $this->_driver->commitTransaction();
         }
@@ -543,9 +453,6 @@ class Connection implements ConnectionInterface
             $this->_transactionLevel = 0;
             $this->_transactionStarted = false;
             $this->nestedTransactionRollbackException = null;
-            if ($this->_logQueries) {
-                $this->log('ROLLBACK');
-            }
             $this->_driver->rollbackTransaction();
 
             return true;
@@ -826,86 +733,6 @@ class Connection implements ConnectionInterface
     }
 
     /**
-     * Enable/disable query logging
-     *
-     * @param bool $enable Enable/disable query logging
-     * @return $this
-     */
-    public function enableQueryLogging(bool $enable = true)
-    {
-        $this->_logQueries = $enable;
-
-        return $this;
-    }
-
-    /**
-     * Disable query logging
-     *
-     * @return $this
-     */
-    public function disableQueryLogging()
-    {
-        $this->_logQueries = false;
-
-        return $this;
-    }
-
-    /**
-     * Check if query logging is enabled.
-     *
-     * @return bool
-     */
-    public function isQueryLoggingEnabled(): bool
-    {
-        return $this->_logQueries;
-    }
-
-    /**
-     * Sets a logger
-     *
-     * @param \Psr\Log\LoggerInterface $logger Logger object
-     * @return void
-     */
-    public function setLogger(LoggerInterface $logger): void
-    {
-        $this->_logger = $logger;
-    }
-
-    /**
-     * Gets the logger object
-     *
-     * @return \Psr\Log\LoggerInterface logger instance
-     */
-    public function getLogger(): LoggerInterface
-    {
-        if ($this->_logger !== null) {
-            return $this->_logger;
-        }
-
-        if (!class_exists(QueryLogger::class)) {
-            throw new RuntimeException(
-                'For logging you must either set a logger using Connection::setLogger()' .
-                ' or require the cakephp/log package in your composer config.'
-            );
-        }
-
-        return $this->_logger = new QueryLogger(['connection' => $this->configName()]);
-    }
-
-    /**
-     * Logs a Query string using the configured logger object.
-     *
-     * @param string $sql string to be logged
-     * @return void
-     */
-    public function log(string $sql): void
-    {
-        $query = new LoggedQuery();
-        $query->query = $sql;
-        $this->getLogger()->debug((string)$query, ['query' => $query]);
-    }
-
-    /**
      * Returns an array that can be used to describe the internal state of this
      * object.
      *
@@ -929,8 +756,6 @@ class Connection implements ConnectionInterface
             'transactionLevel' => $this->_transactionLevel,
             'transactionStarted' => $this->_transactionStarted,
             'useSavePoints' => $this->_useSavePoints,
-            'logQueries' => $this->_logQueries,
-            'logger' => $this->_logger,
         ];
     }
 }

+ 136 - 1
src/Database/Driver.php

@@ -17,8 +17,11 @@ declare(strict_types=1);
 namespace Cake\Database;
 
 use Cake\Core\App;
+use Cake\Core\Exception\CakeException;
 use Cake\Core\Retry\CommandRetry;
 use Cake\Database\Exception\MissingConnectionException;
+use Cake\Database\Log\LoggedQuery;
+use Cake\Database\Log\QueryLogger;
 use Cake\Database\Retry\ErrorCodeWaitStrategy;
 use Cake\Database\Schema\SchemaDialect;
 use Cake\Database\Schema\TableSchema;
@@ -28,6 +31,9 @@ use Closure;
 use InvalidArgumentException;
 use PDO;
 use PDOException;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LoggerInterface;
+use Stringable;
 
 /**
  * Represents a database driver containing all specificities for
@@ -35,6 +41,8 @@ use PDOException;
  */
 abstract class Driver implements DriverInterface
 {
+    use LoggerAwareTrait;
+
     /**
      * @var int|null Maximum alias length or null if no limit
      */
@@ -114,11 +122,14 @@ abstract class Driver implements DriverInterface
                 'Please pass "username" instead of "login" for connecting to the database'
             );
         }
-        $config += $this->_baseConfig;
+        $config += $this->_baseConfig + ['log' => false];
         $this->_config = $config;
         if (!empty($config['quoteIdentifiers'])) {
             $this->enableAutoQuoting();
         }
+        if ($config['log'] !== false) {
+            $this->logger = $this->createLogger($config['log'] === true ? null : $config['log']);
+        }
     }
 
     /**
@@ -218,6 +229,74 @@ abstract class Driver implements DriverInterface
     /**
      * @inheritDoc
      */
+    public function execute(string $sql, array $params = [], array $types = []): StatementInterface
+    {
+        $statement = $this->prepare($sql);
+        if (!empty($params)) {
+            $statement->bind($params, $types);
+        }
+        $this->executeStatement($statement);
+
+        return $statement;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function run(Query $query): StatementInterface
+    {
+        $statement = $this->prepare($query);
+        $query->getValueBinder()->attachTo($statement);
+        $this->executeStatement($statement);
+
+        return $statement;
+    }
+
+    /**
+     * Execute the statement and log the query string.
+     *
+     * @param \Cake\Database\StatementInterface $statement Statement to execute.
+     * @param array|null $params List of values to be bound to query.
+     * @return void
+     */
+    protected function executeStatement(StatementInterface $statement, ?array $params = null): void
+    {
+        if ($this->logger === null) {
+            $statement->execute($params);
+
+            return;
+        }
+
+        $exception = null;
+        $took = 0.0;
+
+        try {
+            $start = microtime(true);
+            $statement->execute($params);
+            $took = (float)number_format((microtime(true) - $start) * 1000, 1);
+        } catch (PDOException $e) {
+            $exception = $e;
+        }
+
+        $logContext = [
+            'driver' => $this,
+            'error' => $exception,
+            'params' => $params ?? $statement->getBoundParams(),
+        ];
+        if (!$exception) {
+            $logContext['numRows'] = $statement->rowCount();
+            $logContext['took'] = $took;
+        }
+        $this->log($statement->queryString(), $logContext);
+
+        if ($exception) {
+            throw $exception;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
     public function prepare(Query|string $query): StatementInterface
     {
         $statement = $this->getPdo()->prepare($query instanceof Query ? $query->sql() : $query);
@@ -240,6 +319,8 @@ abstract class Driver implements DriverInterface
             return true;
         }
 
+        $this->log('BEGIN');
+
         return $this->getPdo()->beginTransaction();
     }
 
@@ -252,6 +333,8 @@ abstract class Driver implements DriverInterface
             return false;
         }
 
+        $this->log('COMMIT');
+
         return $this->getPdo()->commit();
     }
 
@@ -264,6 +347,8 @@ abstract class Driver implements DriverInterface
             return false;
         }
 
+        $this->log('ROLLBACK');
+
         return $this->getPdo()->rollBack();
     }
 
@@ -461,6 +546,56 @@ abstract class Driver implements DriverInterface
     }
 
     /**
+     * @inheritDoc
+     */
+    public function setLogger(LoggerInterface $logger): void
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Create logger instance.
+     *
+     * @param string|null $className Logger's class name
+     * @return \Psr\Log\LoggerInterface
+     */
+    protected function createLogger(?string $className): LoggerInterface
+    {
+        if ($className === null) {
+            $className = QueryLogger::class;
+        }
+
+        /** @var class-string<\Psr\Log\LoggerInterface>|null $className */
+        $className = App::className($className, 'Cake/Log', 'Log');
+        if ($className === null) {
+            throw new CakeException(
+                'For logging you must either set the `log` config to a FQCN which implemnts Psr\Log\LoggerInterface' .
+                ' or require the cakephp/log package in your composer config.'
+            );
+        }
+
+        return new $className();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function log(Stringable|string $message, array $context = []): bool
+    {
+        if ($this->logger === null) {
+            return false;
+        }
+
+        $context['query'] = $message;
+        $loggedQuery = new LoggedQuery();
+        $loggedQuery->setContext($context);
+
+        $this->logger->debug((string)$loggedQuery, ['query' => $loggedQuery]);
+
+        return true;
+    }
+
+    /**
      * Destructor
      */
     public function __destruct()

+ 32 - 1
src/Database/DriverInterface.php

@@ -19,11 +19,13 @@ namespace Cake\Database;
 use Cake\Database\Schema\SchemaDialect;
 use Cake\Database\Schema\TableSchemaInterface;
 use Closure;
+use Psr\Log\LoggerAwareInterface;
+use Stringable;
 
 /**
  * Interface for database driver.
  */
-interface DriverInterface
+interface DriverInterface extends LoggerAwareInterface
 {
     /**
      * Common Table Expressions (with clause) support.
@@ -105,6 +107,26 @@ interface DriverInterface
     public function prepare(Query|string $query): StatementInterface;
 
     /**
+     * Executes a query using $params for interpolating values and $types as a hint for each
+     * those params.
+     *
+     * @param string $sql SQL to be executed and interpolated with $params
+     * @param array $params List or associative array of params to be interpolated in $sql as values.
+     * @param array $types List or associative array of types to be used for casting values in query.
+     * @return \Cake\Database\StatementInterface Executed statement
+     */
+    public function execute(string $sql, array $params = [], array $types = []): StatementInterface;
+
+    /**
+     * Executes the provided query after compiling it for the specific driver
+     * dialect and returns the executed Statement object.
+     *
+     * @param \Cake\Database\Query $query The query to be executed.
+     * @return \Cake\Database\StatementInterface Executed statement
+     */
+    public function run(Query $query): StatementInterface;
+
+    /**
      * Starts a transaction.
      *
      * @return bool True on success, false otherwise.
@@ -302,4 +324,13 @@ interface DriverInterface
      * @return int|null
      */
     public function getMaxAliasLength(): ?int;
+
+    /**
+     * Logs a message or query using the configured logger object.
+     *
+     * @param \Stringable|string $message Message string or query.
+     * @param array $context Logging context.
+     * @return bool True if message was logged.
+     */
+    public function log(Stringable|string $message, array $context = []): bool;
 }

+ 19 - 6
src/Database/Log/LoggedQuery.php

@@ -35,42 +35,42 @@ class LoggedQuery implements JsonSerializable, Stringable
      *
      * @var \Cake\Database\DriverInterface|null
      */
-    public ?DriverInterface $driver = null;
+    protected ?DriverInterface $driver = null;
 
     /**
      * Query string that was executed
      *
      * @var string
      */
-    public string $query = '';
+    protected string $query = '';
 
     /**
      * Number of milliseconds this query took to complete
      *
      * @var float
      */
-    public float $took = 0;
+    protected float $took = 0;
 
     /**
      * Associative array with the params bound to the query string
      *
      * @var array
      */
-    public array $params = [];
+    protected array $params = [];
 
     /**
      * Number of rows affected or returned by the query execution
      *
      * @var int
      */
-    public int $numRows = 0;
+    protected int $numRows = 0;
 
     /**
      * The exception that was thrown by the execution of this query
      *
      * @var \Exception|null
      */
-    public ?Exception $error = null;
+    protected ?Exception $error = null;
 
     /**
      * Helper function used to replace query placeholders by the real
@@ -137,6 +137,19 @@ class LoggedQuery implements JsonSerializable, Stringable
     }
 
     /**
+     * Set logging context for this query.
+     *
+     * @param array $context Context data.
+     * @return void
+     */
+    public function setContext(array $context): void
+    {
+        foreach ($context as $key => $val) {
+            $this->{$key} = $val;
+        }
+    }
+
+    /**
      * Returns data that will be serialized as JSON
      *
      * @return array<string, mixed>

+ 7 - 3
src/Database/Log/QueryLogger.php

@@ -18,6 +18,7 @@ namespace Cake\Database\Log;
 
 use Cake\Log\Engine\BaseLog;
 use Cake\Log\Log;
+use Stringable;
 
 /**
  * This class is a bridge used to write LoggedQuery objects into a real log.
@@ -43,10 +44,13 @@ class QueryLogger extends BaseLog
     /**
      * @inheritDoc
      */
-    public function log($level, $message, array $context = []): void
+    public function log($level, string|Stringable $message, array $context = []): void
     {
-        $context['scope'] = $this->scopes() ?: ['queriesLog'];
-        $context['connection'] = $this->getConfig('connection');
+        $context += [
+            'scope' => $this->scopes() ?: ['queriesLog'],
+            'connection' => $this->getConfig('connection'),
+            'query' => null,
+        ];
 
         if ($context['query'] instanceof LoggedQuery) {
             $context = $context['query']->getContext() + $context;

+ 1 - 1
src/Database/Query.php

@@ -356,7 +356,7 @@ class Query implements ExpressionInterface, IteratorAggregate, Stringable
             $binder->resetCount();
         }
 
-        return $this->getConnection()->compileQuery($this, $binder);
+        return $this->getConnection()->getDriver()->compileQuery($this, $binder)[1];
     }
 
     /**

+ 4 - 1
src/Database/Retry/ReconnectStrategy.php

@@ -110,7 +110,10 @@ class ReconnectStrategy implements RetryStrategyInterface
 
         try {
             $this->connection->connect();
-            $this->connection->log('[RECONNECT]');
+            $this->connection->getDriver()->log(
+                'connection={connection} [RECONNECT]',
+                ['connection' => $this->connection->configName()]
+            );
 
             return true;
         } catch (Exception) {

+ 9 - 53
src/Database/Statement/Statement.php

@@ -18,15 +18,12 @@ namespace Cake\Database\Statement;
 
 use Cake\Database\DriverInterface;
 use Cake\Database\FieldTypeConverter;
-use Cake\Database\Log\LoggedQuery;
 use Cake\Database\StatementInterface;
 use Cake\Database\TypeConverterTrait;
 use Cake\Database\TypeMap;
 use InvalidArgumentException;
 use PDO;
-use PDOException;
 use PDOStatement;
-use Psr\Log\LoggerInterface;
 
 class Statement implements StatementInterface
 {
@@ -60,11 +57,6 @@ class Statement implements StatementInterface
     protected ?FieldTypeConverter $typeConverter;
 
     /**
-     * @var \Psr\Log\LoggerInterface|null
-     */
-    protected ?LoggerInterface $logger = null;
-
-    /**
      * Cached bound parameters used for logging
      *
      * @var array<mixed>
@@ -72,11 +64,6 @@ class Statement implements StatementInterface
     protected array $params = [];
 
     /**
-     * @var float
-     */
-    protected float $took = 0.0;
-
-    /**
      * @param \PDOStatement $statement PDO statement
      * @param \Cake\Database\DriverInterface $driver Database driver
      * @param \Cake\Database\TypeMap|null $typeMap Results type map
@@ -94,16 +81,6 @@ class Statement implements StatementInterface
     /**
      * @inheritDoc
      */
-    public function setLogger(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-
-        return $this;
-    }
-
-    /**
-     * @inheritDoc
-     */
     public function bind(array $params, array $types): void
     {
         if (empty($params)) {
@@ -142,6 +119,14 @@ class Statement implements StatementInterface
     /**
      * @inheritDoc
      */
+    public function getBoundParams(): array
+    {
+        return $this->params;
+    }
+
+    /**
+     * @inheritDoc
+     */
     protected function performBind(string|int $column, mixed $value, int $type): void
     {
         $this->statement->bindValue($column, $value, $type);
@@ -152,36 +137,7 @@ class Statement implements StatementInterface
      */
     public function execute(?array $params = null): bool
     {
-        $success = false;
-        $exception = null;
-
-        try {
-            $start = microtime(true);
-            $success = $this->statement->execute($params);
-            $this->took = (microtime(true) - $start) * 1000;
-        } catch (PDOException $e) {
-            $exception = $e;
-            $this->took = 0.0;
-        }
-
-        if ($this->logger) {
-            $loggedQuery = new LoggedQuery();
-            $loggedQuery->driver = $this->_driver;
-            $loggedQuery->query = $this->queryString();
-            $loggedQuery->params = $params ?? $this->params;
-            $loggedQuery->error = $exception;
-            if (!$exception) {
-                $loggedQuery->numRows = $this->rowCount();
-                $loggedQuery->took = (int)round($this->took);
-            }
-            $this->logger->debug((string)$loggedQuery, ['query' => $loggedQuery]);
-        }
-
-        if ($exception) {
-            throw $exception;
-        }
-
-        return $success;
+        return $this->statement->execute($params);
     }
 
     /**

+ 10 - 5
src/Database/StatementInterface.php

@@ -17,7 +17,6 @@ declare(strict_types=1);
 namespace Cake\Database;
 
 use PDO;
-use Psr\Log\LoggerInterface;
 
 interface StatementInterface
 {
@@ -197,10 +196,16 @@ interface StatementInterface
     public function lastInsertId(?string $table = null, ?string $column = null): string|int;
 
     /**
-     * Sets query logger to use when calling execute().
+     * Returns prepared query string.
      *
-     * @param \Psr\Log\LoggerInterface $logger Query logger
-     * @return $this
+     * @return string
+     */
+    public function queryString(): string;
+
+    /**
+     * Get the bound params.
+     *
+     * @return array
      */
-    public function setLogger(LoggerInterface $logger);
+    public function getBoundParams(): array;
 }

+ 2 - 1
src/Database/composer.json

@@ -26,7 +26,8 @@
     "require": {
         "php": ">=8.0",
         "cakephp/core": "^5.0",
-        "cakephp/datasource": "^5.0"
+        "cakephp/datasource": "^5.0",
+        "psr/log": "^3.0"
     },
     "suggest": {
         "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types."

+ 1 - 32
src/Datasource/ConnectionInterface.php

@@ -16,14 +16,12 @@ declare(strict_types=1);
  */
 namespace Cake\Datasource;
 
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
 use Psr\SimpleCache\CacheInterface;
 
 /**
  * This interface defines the methods you can depend on in a connection.
  */
-interface ConnectionInterface extends LoggerAwareInterface
+interface ConnectionInterface
 {
     /**
      * Gets the driver instance.
@@ -33,13 +31,6 @@ interface ConnectionInterface extends LoggerAwareInterface
     public function getDriver(): object;
 
     /**
-     * Gets the current logger object.
-     *
-     * @return \Psr\Log\LoggerInterface logger instance
-     */
-    public function getLogger(): LoggerInterface;
-
-    /**
      * Set a cacher.
      *
      * @param \Psr\SimpleCache\CacheInterface $cacher Cacher object
@@ -67,26 +58,4 @@ interface ConnectionInterface extends LoggerAwareInterface
      * @return array
      */
     public function config(): array;
-
-    /**
-     * Enable/disable query logging
-     *
-     * @param bool $enable Enable/disable query logging
-     * @return $this
-     */
-    public function enableQueryLogging(bool $enable = true);
-
-    /**
-     * Disable query logging
-     *
-     * @return $this
-     */
-    public function disableQueryLogging();
-
-    /**
-     * Check if query logging is enabled.
-     *
-     * @return bool
-     */
-    public function isQueryLoggingEnabled(): bool;
 }

+ 0 - 1
src/Datasource/composer.json

@@ -26,7 +26,6 @@
     "require": {
         "php": ">=8.0",
         "cakephp/core": "^5.0",
-        "psr/log": "^3.0",
         "psr/simple-cache": "^2.0 || ^3.0"
     },
     "suggest": {

+ 8 - 2
src/TestSuite/ConnectionHelper.php

@@ -17,6 +17,7 @@ namespace Cake\TestSuite;
 
 use Cake\Database\Connection;
 use Cake\Database\DriverInterface;
+use Cake\Database\Log\QueryLogger;
 use Cake\Datasource\ConnectionManager;
 use Closure;
 
@@ -67,8 +68,13 @@ class ConnectionHelper
         $connections = $connections ?? ConnectionManager::configured();
         foreach ($connections as $connection) {
             $connection = ConnectionManager::get($connection);
-            if ($connection instanceof Connection) {
-                $connection->enableQueryLogging();
+            $message = '--Starting test run ' . date('Y-m-d H:i:s');
+            if (
+                $connection instanceof Connection &&
+                $connection->getDriver()->log($message) === false
+            ) {
+                $connection->getDriver()->setLogger(new QueryLogger());
+                $connection->getDriver()->log($message);
             }
         }
     }

+ 7 - 118
tests/TestCase/Database/ConnectionTest.php

@@ -26,7 +26,6 @@ use Cake\Database\Exception\MissingConnectionException;
 use Cake\Database\Exception\MissingDriverException;
 use Cake\Database\Exception\MissingExtensionException;
 use Cake\Database\Exception\NestedTransactionRollbackException;
-use Cake\Database\Log\QueryLogger;
 use Cake\Database\Schema\CachedCollection;
 use Cake\Database\StatementInterface;
 use Cake\Datasource\ConnectionManager;
@@ -70,7 +69,7 @@ class ConnectionTest extends TestCase
     protected $logState;
 
     /**
-     * @var \Cake\Datasource\ConnectionInterface
+     * @var \Cake\Database\Connection
      */
     protected $connection;
 
@@ -83,10 +82,6 @@ class ConnectionTest extends TestCase
     {
         parent::setUp();
         $this->connection = ConnectionManager::get('test');
-        $this->defaultLogger = $this->connection->getLogger();
-
-        $this->logState = $this->connection->isQueryLoggingEnabled();
-        $this->connection->disableQueryLogging();
 
         static::setAppNamespace();
     }
@@ -94,8 +89,6 @@ class ConnectionTest extends TestCase
     public function tearDown(): void
     {
         $this->connection->disableSavePoints();
-        $this->connection->setLogger($this->defaultLogger);
-        $this->connection->enableQueryLogging($this->logState);
 
         Log::reset();
         unset($this->connection);
@@ -216,20 +209,6 @@ class ConnectionTest extends TestCase
     }
 
     /**
-     * Tests creation of prepared statements
-     */
-    public function testPrepare(): void
-    {
-        $sql = 'SELECT 1 + 1';
-        $result = $this->connection->prepare($sql);
-        $this->assertInstanceOf(StatementInterface::class, $result);
-
-        $query = $this->connection->newQuery()->select('1 + 1');
-        $result = $this->connection->prepare($query);
-        $this->assertInstanceOf(StatementInterface::class, $result);
-    }
-
-    /**
      * Tests executing a simple query using bound values
      */
     public function testExecuteWithArguments(): void
@@ -829,96 +808,6 @@ class ConnectionTest extends TestCase
     }
 
     /**
-     * Tests default return vale for logger() function
-     */
-    public function testGetLoggerDefault(): void
-    {
-        $logger = $this->connection->getLogger();
-        $this->assertInstanceOf('Cake\Database\Log\QueryLogger', $logger);
-        $this->assertSame($logger, $this->connection->getLogger());
-    }
-
-    /**
-     * Tests setting and getting the logger object
-     */
-    public function testGetAndSetLogger(): void
-    {
-        $logger = new QueryLogger();
-        $this->connection->setLogger($logger);
-        $this->assertSame($logger, $this->connection->getLogger());
-    }
-
-    /**
-     * test enableQueryLogging method
-     */
-    public function testEnableQueryLogging(): void
-    {
-        $this->connection->enableQueryLogging(true);
-        $this->assertTrue($this->connection->isQueryLoggingEnabled());
-
-        $this->connection->disableQueryLogging();
-        $this->assertFalse($this->connection->isQueryLoggingEnabled());
-    }
-
-    /**
-     * Tests that log() function logs to the configured query logger
-     */
-    public function testLogFunction(): void
-    {
-        Log::setConfig('queries', ['className' => 'Array']);
-        $this->connection->enableQueryLogging();
-        $this->connection->log('SELECT 1');
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(1, $messages);
-        $this->assertSame('debug: connection=test duration=0 rows=0 SELECT 1', $messages[0]);
-    }
-
-    /**
-     * Tests that begin and rollback are also logged
-     */
-    public function testLogBeginRollbackTransaction(): void
-    {
-        Log::setConfig('queries', ['className' => 'Array']);
-
-        $connection = new Connection([
-            'driver' => $this->getMockFormDriver(),
-            'log' => true,
-        ]);
-
-        $connection->begin();
-        $connection->begin(); //This one will not be logged
-        $connection->rollback();
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(2, $messages);
-        $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[0]);
-        $this->assertSame('debug: connection= duration=0 rows=0 ROLLBACK', $messages[1]);
-    }
-
-    /**
-     * Tests that commits are logged
-     */
-    public function testLogCommitTransaction(): void
-    {
-        $driver = $this->getMockFormDriver();
-        $connection = $this->getMockBuilder(Connection::class)
-            ->onlyMethods(['connect'])
-            ->setConstructorArgs([['driver' => $driver]])
-            ->getMock();
-
-        Log::setConfig('queries', ['className' => 'Array']);
-        $connection->enableQueryLogging(true);
-        $connection->begin();
-        $connection->commit();
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(2, $messages);
-        $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[0]);
-        $this->assertSame('debug: connection= duration=0 rows=0 COMMIT', $messages[1]);
-    }
-
-    /**
      * Tests setting and getting the cacher object
      */
     public function testGetAndSetCacher(): void
@@ -1183,7 +1072,7 @@ class ConnectionTest extends TestCase
     public function testAutomaticReconnect(): void
     {
         $conn = clone $this->connection;
-        $statement = $conn->query('SELECT 1');
+        $statement = $conn->execute('SELECT 1');
         $statement->execute();
         $statement->closeCursor();
 
@@ -1194,13 +1083,13 @@ class ConnectionTest extends TestCase
         $prop->setValue($conn, $newDriver);
 
         $newDriver->expects($this->exactly(2))
-            ->method('prepare')
+            ->method('execute')
             ->will($this->onConsecutiveCalls(
                 $this->throwException(new Exception('server gone away')),
                 $this->returnValue($statement)
             ));
 
-        $res = $conn->query('SELECT 1');
+        $res = $conn->execute('SELECT 1');
         $this->assertInstanceOf(StatementInterface::class, $res);
     }
 
@@ -1211,7 +1100,7 @@ class ConnectionTest extends TestCase
     public function testNoAutomaticReconnect(): void
     {
         $conn = clone $this->connection;
-        $statement = $conn->query('SELECT 1');
+        $statement = $conn->execute('SELECT 1');
         $statement->execute();
         $statement->closeCursor();
 
@@ -1224,11 +1113,11 @@ class ConnectionTest extends TestCase
         $prop->setValue($conn, $newDriver);
 
         $newDriver->expects($this->once())
-            ->method('prepare')
+            ->method('execute')
             ->will($this->throwException(new Exception('server gone away')));
 
         try {
-            $conn->query('SELECT 1');
+            $conn->execute('SELECT 1');
         } catch (Exception $e) {
         }
         $this->assertInstanceOf(Exception::class, $e ?? null);

+ 2 - 0
tests/TestCase/Database/Driver/MysqlTest.php

@@ -57,6 +57,7 @@ class MysqlTest extends TestCase
             'encoding' => 'utf8mb4',
             'timezone' => null,
             'init' => [],
+            'log' => false,
         ];
 
         $expected['flags'] += [
@@ -96,6 +97,7 @@ class MysqlTest extends TestCase
                 'Execute this',
                 'this too',
             ],
+            'log' => false,
         ];
         $driver = $this->getMockBuilder('Cake\Database\Driver\Mysql')
             ->onlyMethods(['createPdo'])

+ 2 - 0
tests/TestCase/Database/Driver/PostgresTest.php

@@ -49,6 +49,7 @@ class PostgresTest extends TestCase
             'timezone' => null,
             'flags' => [],
             'init' => [],
+            'log' => false,
         ];
 
         $expected['flags'] += [
@@ -100,6 +101,7 @@ class PostgresTest extends TestCase
             'timezone' => 'Antarctica',
             'schema' => 'fooblic',
             'init' => ['Execute this', 'this too'],
+            'log' => false,
         ];
         $driver = $this->getMockBuilder('Cake\Database\Driver\Postgres')
             ->onlyMethods(['createPdo'])

+ 3 - 2
tests/TestCase/Database/Driver/SqliteTest.php

@@ -55,6 +55,7 @@ class SqliteTest extends TestCase
             'flags' => [],
             'init' => [],
             'mask' => 420,
+            'log' => false,
         ];
 
         $expected['flags'] += [
@@ -88,7 +89,7 @@ class SqliteTest extends TestCase
         $dsn = 'sqlite:bar.db';
 
         $expected = $config;
-        $expected += ['username' => null, 'password' => null, 'cache' => null, 'mode' => null];
+        $expected += ['username' => null, 'password' => null, 'cache' => null, 'mode' => null, 'log' => false];
         $expected['flags'] += [
             PDO::ATTR_PERSISTENT => true,
             PDO::ATTR_EMULATE_PREPARES => false,
@@ -126,7 +127,7 @@ class SqliteTest extends TestCase
         $connection = ConnectionManager::get('test_shared_cache');
         $this->assertSame([], $connection->getSchemaCollection()->listTables());
 
-        $connection->query('CREATE TABLE test (test int);');
+        $connection->execute('CREATE TABLE test (test int);');
         $this->assertSame(['test'], $connection->getSchemaCollection()->listTables());
 
         ConnectionManager::setConfig('test_shared_cache2', [

+ 1 - 0
tests/TestCase/Database/Driver/SqlserverTest.php

@@ -149,6 +149,7 @@ class SqlserverTest extends TestCase
         $expected['loginTimeout'] = null;
         $expected['multiSubnetFailover'] = null;
         $expected['port'] = null;
+        $expected['log'] = false;
 
         $connection = $this->getMockBuilder('PDO')
             ->disableOriginalConstructor()

+ 199 - 1
tests/TestCase/Database/DriverTest.php

@@ -20,14 +20,19 @@ use Cake\Database\Driver;
 use Cake\Database\Driver\Sqlserver;
 use Cake\Database\DriverInterface;
 use Cake\Database\Exception\MissingConnectionException;
+use Cake\Database\Log\QueryLogger;
 use Cake\Database\Query;
 use Cake\Database\QueryCompiler;
 use Cake\Database\Schema\TableSchema;
+use Cake\Database\Statement\Statement;
 use Cake\Database\ValueBinder;
 use Cake\Datasource\ConnectionManager;
+use Cake\Log\Log;
 use Cake\TestSuite\TestCase;
+use DateTime;
 use Exception;
 use PDO;
+use PDOException;
 use PDOStatement;
 use TestApp\Database\Driver\RetryDriver;
 use TestApp\Database\Driver\StubDriver;
@@ -49,6 +54,11 @@ class DriverTest extends TestCase
     {
         parent::setUp();
 
+        Log::setConfig('queries', [
+            'className' => 'Array',
+            'scopes' => ['queriesLog'],
+        ]);
+
         $this->driver = $this->getMockForAbstractClass(
             StubDriver::class,
             [],
@@ -56,10 +66,16 @@ class DriverTest extends TestCase
             true,
             true,
             true,
-            ['createPdo']
+            ['createPdo', 'prepare']
         );
     }
 
+    public function tearDown(): void
+    {
+        parent::tearDown();
+        Log::drop('queries');
+    }
+
     /**
      * Test if building the object throws an exception if we're not passing
      * required config data.
@@ -310,4 +326,186 @@ class DriverTest extends TestCase
             ['42', '42'],
         ];
     }
+
+    /**
+     * Tests that queries are logged when executed without params
+     */
+    public function testExecuteNoParams(): void
+    {
+        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
+
+        $statement = $this->getMockBuilder(Statement::class)
+            ->setConstructorArgs([$inner, $this->driver])
+            ->onlyMethods(['queryString','rowCount','execute'])
+            ->getMock();
+        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
+        $statement->method('rowCount')->will($this->returnValue(3));
+        $statement->method('execute')->will($this->returnValue(true));
+
+        $this->driver->expects($this->any())
+            ->method('prepare')
+            ->willReturn($statement);
+        $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
+
+        $this->driver->execute('SELECT bar FROM foo');
+
+        $messages = Log::engine('queries')->read();
+        $this->assertCount(1, $messages);
+        $this->assertMatchesRegularExpression('/^debug: connection=test duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
+    }
+
+    /**
+     * Tests that queries are logged when executed with bound params
+     */
+    public function testExecuteWithBinding(): void
+    {
+        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
+
+        $statement = $this->getMockBuilder(Statement::class)
+            ->setConstructorArgs([$inner, $this->driver])
+            ->onlyMethods(['queryString','rowCount','execute'])
+            ->getMock();
+        $statement->method('rowCount')->will($this->returnValue(3));
+        $statement->method('execute')->will($this->returnValue(true));
+        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo WHERE a=:a AND b=:b'));
+
+        $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
+        $this->driver->expects($this->any())
+            ->method('prepare')
+            ->willReturn($statement);
+
+        $this->driver->execute(
+            'SELECT bar FROM foo WHERE a=:a AND b=:b',
+            [
+                'a' => 1,
+                'b' => new DateTime('2013-01-01'),
+            ],
+            ['b' => 'date']
+        );
+
+        $messages = Log::engine('queries')->read();
+        $this->assertCount(1, $messages);
+        $this->assertMatchesRegularExpression("/^debug: connection=test duration=\d+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
+    }
+
+    /**
+     * Tests that queries are logged despite database errors
+     */
+    public function testExecuteWithError(): void
+    {
+        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
+
+        $statement = $this->getMockBuilder(Statement::class)
+            ->setConstructorArgs([$inner, $this->driver])
+            ->onlyMethods(['queryString','rowCount','execute'])
+            ->getMock();
+        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
+        $statement->method('rowCount')->will($this->returnValue(0));
+        $statement->method('execute')->will($this->throwException(new PDOException()));
+
+        $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
+        $this->driver->expects($this->any())
+            ->method('prepare')
+            ->willReturn($statement);
+
+        try {
+            $this->driver->execute('SELECT foo FROM bar');
+        } catch (PDOException $e) {
+        }
+
+        $messages = Log::engine('queries')->read();
+        $this->assertCount(1, $messages);
+        $this->assertMatchesRegularExpression('/^debug: connection=test duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
+    }
+
+    public function testGetLoggerDefault(): void
+    {
+        $driver = $this->getMockForAbstractClass(
+            StubDriver::class,
+            [],
+            '',
+            true,
+            true,
+            true,
+            ['createPdo', 'prepare']
+        );
+        $this->assertNull($driver->getLogger());
+
+        $driver = $this->getMockForAbstractClass(
+            StubDriver::class,
+            [['log' => true]],
+            '',
+            true,
+            true,
+            true,
+            ['createPdo']
+        );
+
+        $logger = $driver->getLogger();
+        $this->assertInstanceOf(QueryLogger::class, $logger);
+    }
+
+    public function testSetLogger(): void
+    {
+        $logger = new QueryLogger();
+        $this->driver->setLogger($logger);
+        $this->assertSame($logger, $this->driver->getLogger());
+    }
+
+    public function testLogTransaction(): void
+    {
+        $pdo = $this->getMockBuilder(PDO::class)
+            ->disableOriginalConstructor()
+            ->onlyMethods(['beginTransaction', 'commit', 'rollback', 'inTransaction'])
+            ->getMock();
+        $pdo
+            ->expects($this->any())
+            ->method('beginTransaction')
+            ->willReturn(true);
+        $pdo
+            ->expects($this->any())
+            ->method('commit')
+            ->willReturn(true);
+        $pdo
+            ->expects($this->any())
+            ->method('rollBack')
+            ->willReturn(true);
+        $pdo->expects($this->exactly(5))
+            ->method('inTransaction')
+            ->will($this->onConsecutiveCalls(
+                false,
+                true,
+                true,
+                false,
+                true,
+            ));
+
+        $driver = $this->getMockForAbstractClass(
+            StubDriver::class,
+            [['log' => true]],
+            '',
+            true,
+            true,
+            true,
+            ['getPdo']
+        );
+
+        $driver->expects($this->any())
+            ->method('getPdo')
+            ->willReturn($pdo);
+
+        $driver->beginTransaction();
+        $driver->beginTransaction(); //This one will not be logged
+        $driver->rollbackTransaction();
+
+        $driver->beginTransaction();
+        $driver->commitTransaction();
+
+        $messages = Log::engine('queries')->read();
+        $this->assertCount(4, $messages);
+        $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[0]);
+        $this->assertSame('debug: connection= duration=0 rows=0 ROLLBACK', $messages[1]);
+        $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[2]);
+        $this->assertSame('debug: connection= duration=0 rows=0 COMMIT', $messages[3]);
+    }
 }

+ 49 - 29
tests/TestCase/Database/Log/LoggedQueryTest.php

@@ -50,7 +50,7 @@ class LoggedQueryTest extends TestCase
     public function testStringConversion(): void
     {
         $logged = new LoggedQuery();
-        $logged->query = 'SELECT foo FROM bar';
+        $logged->setContext(['query' => 'SELECT foo FROM bar']);
         $this->assertSame('SELECT foo FROM bar', (string)$logged);
     }
 
@@ -60,9 +60,11 @@ class LoggedQueryTest extends TestCase
     public function testStringInterpolation(): void
     {
         $query = new LoggedQuery();
-        $query->driver = $this->driver;
-        $query->query = 'SELECT a FROM b where a = :p1 AND b = :p2 AND c = :p3 AND d = :p4 AND e = :p5 AND f = :p6';
-        $query->params = ['p1' => 'string', 'p3' => null, 'p2' => 3, 'p4' => true, 'p5' => false, 'p6' => 0];
+        $query->setContext([
+            'driver' => $this->driver,
+            'query' => 'SELECT a FROM b where a = :p1 AND b = :p2 AND c = :p3 AND d = :p4 AND e = :p5 AND f = :p6',
+            'params' => ['p1' => 'string', 'p3' => null, 'p2' => 3, 'p4' => true, 'p5' => false, 'p6' => 0],
+        ]);
 
         $expected = "SELECT a FROM b where a = 'string' AND b = 3 AND c = NULL AND d = $this->true AND e = $this->false AND f = 0";
         $this->assertSame($expected, (string)$query);
@@ -74,9 +76,11 @@ class LoggedQueryTest extends TestCase
     public function testStringInterpolationNotNamed(): void
     {
         $query = new LoggedQuery();
-        $query->driver = $this->driver;
-        $query->query = 'SELECT a FROM b where a = ? AND b = ? AND c = ? AND d = ? AND e = ? AND f = ?';
-        $query->params = ['string', '3', null, true, false, 0];
+        $query->setContext([
+            'driver' => $this->driver,
+            'query' => 'SELECT a FROM b where a = ? AND b = ? AND c = ? AND d = ? AND e = ? AND f = ?',
+            'params' => ['string', '3', null, true, false, 0],
+        ]);
 
         $expected = "SELECT a FROM b where a = 'string' AND b = '3' AND c = NULL AND d = $this->true AND e = $this->false AND f = 0";
         $this->assertSame($expected, (string)$query);
@@ -88,8 +92,10 @@ class LoggedQueryTest extends TestCase
     public function testStringInterpolationDuplicate(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1 AND b = :p1 AND c = :p2 AND d = :p2';
-        $query->params = ['p1' => 'string', 'p2' => 3];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1 AND b = :p1 AND c = :p2 AND d = :p2',
+            'params' => ['p1' => 'string', 'p2' => 3],
+        ]);
 
         $expected = "SELECT a FROM b where a = 'string' AND b = 'string' AND c = 3 AND d = 3";
         $this->assertSame($expected, (string)$query);
@@ -101,8 +107,10 @@ class LoggedQueryTest extends TestCase
     public function testStringInterpolationNamed(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1 AND b = :p11 AND c = :p20 AND d = :p2';
-        $query->params = ['p11' => 'test', 'p1' => 'string', 'p2' => 3, 'p20' => 5];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1 AND b = :p11 AND c = :p20 AND d = :p2',
+            'params' => ['p11' => 'test', 'p1' => 'string', 'p2' => 3, 'p20' => 5],
+        ]);
 
         $expected = "SELECT a FROM b where a = 'string' AND b = 'test' AND c = 5 AND d = 3";
         $this->assertSame($expected, (string)$query);
@@ -114,8 +122,10 @@ class LoggedQueryTest extends TestCase
     public function testStringInterpolationSpecialChars(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1 AND b = :p2 AND c = :p3 AND d = :p4';
-        $query->params = ['p1' => '$2y$10$dUAIj', 'p2' => '$0.23', 'p3' => 'a\\0b\\1c\\d', 'p4' => "a'b"];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1 AND b = :p2 AND c = :p3 AND d = :p4',
+            'params' => ['p1' => '$2y$10$dUAIj', 'p2' => '$0.23', 'p3' => 'a\\0b\\1c\\d', 'p4' => "a'b"],
+        ]);
 
         $expected = "SELECT a FROM b where a = '\$2y\$10\$dUAIj' AND b = '\$0.23' AND c = 'a\\\\0b\\\\1c\\\\d' AND d = 'a''b'";
         $this->assertSame($expected, (string)$query);
@@ -127,9 +137,11 @@ class LoggedQueryTest extends TestCase
     public function testBinaryInterpolation(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1';
         $uuid = str_replace('-', '', Text::uuid());
-        $query->params = ['p1' => hex2bin($uuid)];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1',
+            'params' => ['p1' => hex2bin($uuid)],
+        ]);
 
         $expected = "SELECT a FROM b where a = '{$uuid}'";
         $this->assertSame($expected, (string)$query);
@@ -141,8 +153,10 @@ class LoggedQueryTest extends TestCase
     public function testBinaryInterpolationIgnored(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1';
-        $query->params = ['p1' => "a\tz"];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1',
+            'params' => ['p1' => "a\tz"],
+        ]);
 
         $expected = "SELECT a FROM b where a = 'a\tz'";
         $this->assertSame($expected, (string)$query);
@@ -151,9 +165,11 @@ class LoggedQueryTest extends TestCase
     public function testGetContext(): void
     {
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1';
-        $query->numRows = 10;
-        $query->took = 15;
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1',
+            'numRows' => 10,
+            'took' => 15,
+        ]);
 
         $expected = [
             'numRows' => 10,
@@ -164,21 +180,25 @@ class LoggedQueryTest extends TestCase
 
     public function testJsonSerialize(): void
     {
+        $error = new Exception('You fail!');
+
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = :p1';
-        $query->params = ['p1' => '$2y$10$dUAIj'];
-        $query->numRows = 4;
-        $query->error = new Exception('You fail!');
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = :p1',
+            'params' => ['p1' => '$2y$10$dUAIj'],
+            'numRows' => 4,
+            'error' => $error,
+        ]);
 
         $expected = json_encode([
-            'query' => $query->query,
+            'query' => 'SELECT a FROM b where a = :p1',
             'numRows' => 4,
-            'params' => $query->params,
+            'params' => ['p1' => '$2y$10$dUAIj'],
             'took' => 0,
             'error' => [
-                'class' => get_class($query->error),
-                'message' => $query->error->getMessage(),
-                'code' => $query->error->getCode(),
+                'class' => get_class($error),
+                'message' => $error->getMessage(),
+                'code' => $error->getCode(),
             ],
         ]);
 

+ 5 - 3
tests/TestCase/Database/Log/QueryLoggerTest.php

@@ -45,8 +45,10 @@ class QueryLoggerTest extends TestCase
     {
         $logger = new QueryLogger(['connection' => '']);
         $query = new LoggedQuery();
-        $query->query = 'SELECT a FROM b where a = ? AND b = ? AND c = ?';
-        $query->params = ['string', '3', null];
+        $query->setContext([
+            'query' => 'SELECT a FROM b where a = ? AND b = ? AND c = ?',
+            'params' => ['string', '3', null],
+        ]);
 
         Log::setConfig('queryLoggerTest', [
             'className' => 'Array',
@@ -69,7 +71,7 @@ class QueryLoggerTest extends TestCase
     {
         $logger = new QueryLogger(['connection' => 'test']);
         $query = new LoggedQuery();
-        $query->query = 'SELECT a';
+        $query->setContext(['query' => 'SELECT a']);
 
         Log::setConfig('queryLoggerTest', [
             'className' => 'Array',

+ 0 - 124
tests/TestCase/Database/Statement/StatementTest.php

@@ -1,124 +0,0 @@
-<?php
-declare(strict_types=1);
-
-/**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link          https://cakephp.org CakePHP(tm) Project
- * @since         5.0.0
- * @license       https://opensource.org/licenses/mit-license.php MIT License
- */
-namespace Cake\Test\TestCase\Database\Statement;
-
-use Cake\Database\DriverInterface;
-use Cake\Database\Log\QueryLogger;
-use Cake\Database\Statement\Statement;
-use Cake\Log\Log;
-use Cake\TestSuite\TestCase;
-use DateTime;
-use PDOException;
-use PDOStatement;
-
-class StatementTest extends TestCase
-{
-    public function setUp(): void
-    {
-        parent::setUp();
-        Log::setConfig('queries', [
-            'className' => 'Array',
-            'scopes' => ['queriesLog'],
-        ]);
-    }
-
-    public function tearDown(): void
-    {
-        parent::tearDown();
-        Log::drop('queries');
-    }
-
-    /**
-     * Tests that queries are logged when executed without params
-     */
-    public function testExecuteNoParams(): void
-    {
-        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
-        $inner->method('rowCount')->will($this->returnValue(3));
-        $inner->method('execute')->will($this->returnValue(true));
-
-        $driver = $this->getMockBuilder(DriverInterface::class)->getMock();
-        $statement = $this->getMockBuilder(Statement::class)
-            ->setConstructorArgs([$inner, $driver])
-            ->onlyMethods(['queryString'])
-            ->getMock();
-        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
-        $statement->setLogger(new QueryLogger(['connection' => 'test']));
-        $statement->execute();
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(1, $messages);
-        $this->assertMatchesRegularExpression('/^debug: connection=test duration=\d+ rows=3 SELECT bar FROM foo$/', $messages[0]);
-    }
-
-    /**
-     * Tests that queries are logged when executed with bound params
-     */
-    public function testExecuteWithBinding(): void
-    {
-        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
-        $inner->method('rowCount')->will($this->returnValue(3));
-        $inner->method('execute')->will($this->returnValue(true));
-
-        $driver = $this->getMockBuilder(DriverInterface::class)->getMock();
-        $statement = $this->getMockBuilder(Statement::class)
-            ->setConstructorArgs([$inner, $driver])
-            ->onlyMethods(['queryString'])
-            ->getMock();
-        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo WHERE a=:a AND b=:b'));
-        $statement->setLogger(new QueryLogger(['connection' => 'test']));
-
-        $statement->bindValue('a', 1);
-        $statement->bindValue('b', new DateTime('2013-01-01'), 'date');
-        $statement->execute();
-
-        $statement->bindValue('b', new DateTime('2014-01-01'), 'date');
-        $statement->execute();
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(2, $messages);
-        $this->assertMatchesRegularExpression("/^debug: connection=test duration=\d+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
-        $this->assertMatchesRegularExpression("/^debug: connection=test duration=\d+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2014-01-01'$/", $messages[1]);
-    }
-
-    /**
-     * Tests that queries are logged despite database errors
-     */
-    public function testExecuteWithError(): void
-    {
-        $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
-        $inner->method('rowCount')->will($this->returnValue(3));
-        $inner->method('execute')->will($this->throwException(new PDOException()));
-
-        $driver = $this->getMockBuilder(DriverInterface::class)->getMock();
-        $statement = $this->getMockBuilder(Statement::class)
-            ->setConstructorArgs([$inner, $driver])
-            ->onlyMethods(['queryString'])
-            ->getMock();
-        $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
-        $statement->setLogger(new QueryLogger(['connection' => 'test']));
-
-        try {
-            $statement->execute();
-        } catch (PDOException $e) {
-        }
-
-        $messages = Log::engine('queries')->read();
-        $this->assertCount(1, $messages);
-        $this->assertMatchesRegularExpression('/^debug: connection=test duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
-    }
-}

+ 1 - 7
tests/TestCase/ORM/ResultSetFactoryTest.php

@@ -217,12 +217,8 @@ class ResultSetFactoryTest extends TestCase
     {
         Log::setConfig('queries', ['className' => 'Array']);
 
-        $defaultLogger = $this->connection->getLogger();
-        $queryLogging = $this->connection->isQueryLoggingEnabled();
-
         $logger = new QueryLogger();
-        $this->connection->setLogger($logger);
-        $this->connection->enableQueryLogging(true);
+        $this->connection->getDriver()->setLogger($logger);
 
         $messages = Log::engine('queries')->read();
         $this->assertCount(0, $messages);
@@ -237,8 +233,6 @@ class ResultSetFactoryTest extends TestCase
         $message = array_pop($messages);
         $this->assertStringContainsString('SELECT', $message);
 
-        $this->connection->setLogger($defaultLogger);
-        $this->connection->enableQueryLogging($queryLogging);
         Log::reset();
     }
 }

+ 2 - 2
tests/TestCase/TestSuite/ConnectionHelperTest.php

@@ -44,9 +44,9 @@ class ConnectionHelperTest extends TestCase
     {
         $connection = new Connection(['driver' => TestDriver::class]);
         ConnectionManager::setConfig('query_logging', $connection);
-        $this->assertFalse($connection->isQueryLoggingEnabled());
+        $this->assertFalse($connection->getDriver()->log(''));
 
         (new ConnectionHelper())->enableQueryLogging(['query_logging']);
-        $this->assertTrue($connection->isQueryLoggingEnabled());
+        $this->assertTrue($connection->getDriver()->log(''));
     }
 }

+ 1 - 1
tests/TestCase/TestSuite/Fixture/SchemaLoaderTest.php

@@ -112,7 +112,7 @@ class SchemaLoaderTest extends TestCase
         $result = $connection->getSchemaCollection()->listTables();
         $this->assertEquals(['schema_loader_second'], $result);
 
-        $statement = $connection->query('SELECT * FROM schema_loader_second');
+        $statement = $connection->execute('SELECT * FROM schema_loader_second');
         $result = $statement->fetchAll();
         $this->assertCount(0, $result, 'Table should be empty.');
     }

+ 6 - 0
tests/test_app/TestApp/Database/Driver/StubDriver.php

@@ -16,6 +16,7 @@ declare(strict_types=1);
 namespace TestApp\Database\Driver;
 
 use Cake\Database\Driver;
+use Psr\Log\LoggerInterface;
 
 abstract class StubDriver extends Driver
 {
@@ -23,4 +24,9 @@ abstract class StubDriver extends Driver
     {
         $this->pdo = $this->createPdo('', []);
     }
+
+    public function getLogger(): ?LoggerInterface
+    {
+        return $this->logger;
+    }
 }