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(),
+ );
+ }
}