diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index 08e1a4cee0..8feed762f8 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -175,6 +175,13 @@ class QueryBuilder */ private ?QueryCacheProfile $resultCacheProfile = null; + /** + * The comment tags to be added to the SQL query. + * + * @var string[] + */ + private array $commentTags = []; + /** * Initializes a new QueryBuilder. * @@ -358,13 +365,19 @@ public function executeStatement(): int|string */ public function getSQL(): string { - return $this->sql ??= match ($this->type) { + if ($this->sql !== null) { + return $this->sql; + } + + $sql = match ($this->type) { QueryType::INSERT => $this->getSQLForInsert(), QueryType::DELETE => $this->getSQLForDelete(), QueryType::UPDATE => $this->getSQLForUpdate(), QueryType::SELECT => $this->getSQLForSelect(), QueryType::UNION => $this->getSQLForUnion(), }; + + return $this->sql = $this->addCommentTagsToSQL($sql); } /** @@ -1661,4 +1674,52 @@ public function disableResultCache(): self return $this; } + + /** + * Adds a comment tag to the SQL query. + * + * This method adds a SQL comment that will be prepended to the generated SQL query. + * Multiple tags can be added and will appear in the order they were added. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id', 'u.name') + * ->from('users', 'u') + * ->tagWith('This is a custom tag') + * ->tagWith('Another tag'); + * + * + * @param string $tag The comment tag to add to the query. + * + * @return $this This QueryBuilder instance. + */ + public function tagWith(string $tag): self + { + $this->commentTags[] = $tag; + + $this->sql = null; + + return $this; + } + + /** + * Adds comment tags to the SQL query. + * + * @param string $sql The SQL query to add tags to. + * + * @return string The SQL query with comment tags prepended. + */ + private function addCommentTagsToSQL(string $sql): string + { + if (count($this->commentTags) === 0) { + return $sql; + } + + $commentLines = []; + foreach ($this->commentTags as $tag) { + $commentLines[] = '-- ' . $tag; + } + + return implode("\n", $commentLines) . "\n\n" . $sql; + } } diff --git a/tests/Query/QueryBuilderTest.php b/tests/Query/QueryBuilderTest.php index f0d9f9f203..a1c9cfefc5 100644 --- a/tests/Query/QueryBuilderTest.php +++ b/tests/Query/QueryBuilderTest.php @@ -1546,4 +1546,126 @@ public function testUnionAndOrderByReturnsUnionQueryWithOrderBy(): void $qb->getSQL(), ); } + + public function testTagWithSingleTag(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id', 'u.name') + ->from('users', 'u') + ->tagWith('This is a test query'); + + self::assertEquals( + "-- This is a test query\n\nSELECT u.id, u.name FROM users u", + $qb->getSQL(), + ); + } + + public function testTagWithMultipleTags(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id', 'u.name') + ->from('users', 'u') + ->tagWith('First tag') + ->tagWith('Second tag') + ->tagWith('Third tag'); + + self::assertEquals( + "-- First tag\n-- Second tag\n-- Third tag\n\nSELECT u.id, u.name FROM users u", + $qb->getSQL(), + ); + } + + public function testTagWithInsertQuery(): void + { + $qb = new QueryBuilder($this->conn); + $qb->insert('users') + ->values(['name' => '?', 'email' => '?']) + ->tagWith('Insert operation'); + + self::assertEquals( + "-- Insert operation\n\nINSERT INTO users (name, email) VALUES(?, ?)", + $qb->getSQL(), + ); + } + + public function testTagWithUpdateQuery(): void + { + $qb = new QueryBuilder($this->conn); + $qb->update('users') + ->set('name', '?') + ->where('id = ?') + ->tagWith('Update operation'); + + self::assertEquals( + "-- Update operation\n\nUPDATE users SET name = ? WHERE id = ?", + $qb->getSQL(), + ); + } + + public function testTagWithDeleteQuery(): void + { + $qb = new QueryBuilder($this->conn); + $qb->delete('users') + ->where('id = ?') + ->tagWith('Delete operation'); + + self::assertEquals( + "-- Delete operation\n\nDELETE FROM users WHERE id = ?", + $qb->getSQL(), + ); + } + + public function testTagWithNoTags(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id', 'u.name') + ->from('users', 'u'); + + self::assertEquals( + 'SELECT u.id, u.name FROM users u', + $qb->getSQL(), + ); + } + + public function testTagWithMethodChaining(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id') + ->from('users', 'u') + ->tagWith('Tag 1') + ->where('u.id = ?') + ->tagWith('Tag 2') + ->orderBy('u.name'); + + self::assertEquals( + "-- Tag 1\n-- Tag 2\n\nSELECT u.id FROM users u WHERE u.id = ? ORDER BY u.name", + $qb->getSQL(), + ); + } + + public function testTagWithEmptyTag(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id') + ->from('users', 'u') + ->tagWith(''); + + self::assertEquals( + "-- \n\nSELECT u.id FROM users u", + $qb->getSQL(), + ); + } + + public function testTagWithSpecialCharacters(): void + { + $qb = new QueryBuilder($this->conn); + $qb->select('u.id') + ->from('users', 'u') + ->tagWith("Tag with 'quotes' and \"double quotes\""); + + self::assertEquals( + "-- Tag with 'quotes' and \"double quotes\"\n\nSELECT u.id FROM users u", + $qb->getSQL(), + ); + } }