From e699bf69d3ac3e87b6507296ee40d805478681f7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 4 Jul 2024 17:18:22 +0200 Subject: [PATCH] feat: allow running QueryBuilder queries on different connections Signed-off-by: Robin Appelman --- .../DB/QueryBuilder/ExtendedQueryBuilder.php | 15 ++-- lib/private/DB/QueryBuilder/QueryBuilder.php | 76 ++++++++++--------- lib/public/DB/QueryBuilder/IQueryBuilder.php | 10 ++- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/lib/private/DB/QueryBuilder/ExtendedQueryBuilder.php b/lib/private/DB/QueryBuilder/ExtendedQueryBuilder.php index ab58773dfd3ee..bde6523567f61 100644 --- a/lib/private/DB/QueryBuilder/ExtendedQueryBuilder.php +++ b/lib/private/DB/QueryBuilder/ExtendedQueryBuilder.php @@ -11,6 +11,7 @@ use OC\DB\Exceptions\DbalException; use OCP\DB\IResult; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; /** * Base class for creating classes that extend the builtin query builder @@ -46,12 +47,12 @@ public function getState() { return $this->builder->getState(); } - public function execute() { + public function execute(?IDBConnection $connection = null) { try { if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::SELECT) { - return $this->executeQuery(); + return $this->executeQuery($connection); } else { - return $this->executeStatement(); + return $this->executeStatement($connection); } } catch (DBALException $e) { // `IQueryBuilder->execute` never wrapped the exception, but `executeQuery` and `executeStatement` do @@ -280,11 +281,11 @@ public function getColumnName($column, $tableAlias = '') { return $this->builder->getColumnName($column, $tableAlias); } - public function executeQuery(): IResult { - return $this->builder->executeQuery(); + public function executeQuery(?IDBConnection $connection = null): IResult { + return $this->builder->executeQuery($connection); } - public function executeStatement(): int { - return $this->builder->executeStatement(); + public function executeStatement(?IDBConnection $connection = null): int { + return $this->builder->executeStatement($connection); } } diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index 0e7d8d2ff3eca..82127078d0618 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Query\QueryException; use OC\DB\ConnectionAdapter; +use OC\DB\Exceptions\DbalException; use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\MySqlExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\OCIExpressionBuilder; @@ -22,7 +23,6 @@ use OC\DB\QueryBuilder\FunctionBuilder\OCIFunctionBuilder; use OC\DB\QueryBuilder\FunctionBuilder\PgSqlFunctionBuilder; use OC\DB\QueryBuilder\FunctionBuilder\SqliteFunctionBuilder; -use OC\DB\ResultAdapter; use OC\SystemConfig; use OCP\DB\IResult; use OCP\DB\QueryBuilder\ICompositeExpression; @@ -30,6 +30,7 @@ use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; +use OCP\IDBConnection; use Psr\Log\LoggerInterface; class QueryBuilder implements IQueryBuilder { @@ -168,15 +169,7 @@ public function getState() { return $this->queryBuilder->getState(); } - /** - * Executes this query using the bound parameters and their types. - * - * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate} - * for insert, update and delete statements. - * - * @return IResult|int - */ - public function execute() { + private function prepareForExecute() { if ($this->systemConfig->getValue('log_query', false)) { try { $params = []; @@ -253,48 +246,63 @@ public function execute() { 'exception' => $exception, ]); } + } - $result = $this->queryBuilder->execute(); - if (is_int($result)) { - return $result; + /** + * Executes this query using the bound parameters and their types. + * + * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate} + * for insert, update and delete statements. + * + * @return IResult|int + */ + public function execute(?IDBConnection $connection = null) { + try { + if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::SELECT) { + return $this->executeQuery($connection); + } else { + return $this->executeStatement($connection); + } + } catch (DBALException $e) { + // `IQueryBuilder->execute` never wrapped the exception, but `executeQuery` and `executeStatement` do + /** @var \Doctrine\DBAL\Exception $previous */ + $previous = $e->getPrevious(); + throw $previous; } - return new ResultAdapter($result); } - public function executeQuery(): IResult { + public function executeQuery(?IDBConnection $connection = null): IResult { if ($this->getType() !== \Doctrine\DBAL\Query\QueryBuilder::SELECT) { throw new \RuntimeException('Invalid query type, expected SELECT query'); } - try { - $result = $this->execute(); - } catch (\Doctrine\DBAL\Exception $e) { - throw \OC\DB\Exceptions\DbalException::wrap($e); + $this->prepareForExecute(); + if (!$connection) { + $connection = $this->connection; } - if ($result instanceof IResult) { - return $result; - } - - throw new \RuntimeException('Invalid return type for query'); + return $connection->executeQuery( + $this->getSQL(), + $this->getParameters(), + $this->getParameterTypes(), + ); } - public function executeStatement(): int { + public function executeStatement(?IDBConnection $connection = null): int { if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::SELECT) { throw new \RuntimeException('Invalid query type, expected INSERT, DELETE or UPDATE statement'); } - try { - $result = $this->execute(); - } catch (\Doctrine\DBAL\Exception $e) { - throw \OC\DB\Exceptions\DbalException::wrap($e); - } - - if (!is_int($result)) { - throw new \RuntimeException('Invalid return type for statement'); + $this->prepareForExecute(); + if (!$connection) { + $connection = $this->connection; } - return $result; + return $connection->executeStatement( + $this->getSQL(), + $this->getParameters(), + $this->getParameterTypes(), + ); } diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php index 94ab796adf4c0..c736d3094e553 100644 --- a/lib/public/DB/QueryBuilder/IQueryBuilder.php +++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php @@ -12,6 +12,7 @@ use Doctrine\DBAL\ParameterType; use OCP\DB\Exception; use OCP\DB\IResult; +use OCP\IDBConnection; /** * This class provides a wrapper around Doctrine's QueryBuilder @@ -146,34 +147,37 @@ public function getState(); * that interface changed in a breaking way the adapter \OCP\DB\QueryBuilder\IStatement is returned * to bridge old code to the new API * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return IResult|int * @throws Exception since 21.0.0 * @since 8.2.0 * @deprecated 22.0.0 Use executeQuery or executeStatement */ - public function execute(); + public function execute(?IDBConnection $connection = null); /** * Execute for select statements * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return IResult * @since 22.0.0 * * @throws Exception * @throws \RuntimeException in case of usage with non select query */ - public function executeQuery(): IResult; + public function executeQuery(?IDBConnection $connection = null): IResult; /** * Execute insert, update and delete statements * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return int the number of affected rows * @since 22.0.0 * * @throws Exception * @throws \RuntimeException in case of usage with select query */ - public function executeStatement(): int; + public function executeStatement(?IDBConnection $connection = null): int; /** * Gets the complete SQL string formed by the current specifications of this QueryBuilder.