feat: add Query Builder exists and doesntExist methods#10215
Conversation
- Add exists() and doesntExist() to Query Builder. - Compile lightweight existence probes while preserving builder state. - Support limit, offset, group, having, union, test mode, and reset behavior. - Document the new methods and add focused builder/live tests. Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
| * | ||
| * @return bool|string SQL string when test mode is enabled. | ||
| */ | ||
| public function exists(bool $reset = true) |
There was a problem hiding this comment.
Naming-wise, I wonder whether hasRows() / hasNoRows() would fit this behavior better than exists() / doesntExist(). Since the method checks whether the current Query Builder would return a row, and we already have whereExists() for SQL EXISTS predicates, hasRows() feels less ambiguous to me. Eventually hasResults() / hasNoResults()?
There was a problem hiding this comment.
I thought about this a bit more, and I still slightly lean toward exists() / doesntExist() from a DX point of view.
The main reason is familiarity. Exists is already common vocabulary in SQL, but it's also used across many ecosystems and frameworks. Laravel has exists(), Rails has exists?, Django has exists(), and many developers coming from ORM/query builder backgrounds are already used to that wording.
So if I were asking "does this query return anything?", exists() would probably be one of the first method names I'd try or search for.
hasRows() / hasNoRows() is clear too, so I don't think it's a bad option. It just feels a bit more CI-specific to me, while exists() feels closer to the common vocabulary developers already bring from SQL and other ecosystems. I also like that it sits naturally next to whereExists(), even though one executes the query and the other only adds a predicate.
That said, this is just my preference. I'd be happy to follow whatever the team feels is the better fit.
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Description
This PR proposes adding
exists()anddoesntExist()to Query Builder.These methods make the common "does anything match this builder?" check simple without forcing users to fetch rows or write a count query manually:
The implementation compiles a lightweight existence probe, preserves the builder state when
$resetisfalse, and follows the existing Query Builder test-mode behavior by returning SQL instead of executing it.It also handles less obvious builder shapes like
limit()/offset(), grouped queries,having(), unions, reset behavior, and failed query execution. If the existence probe cannot be executed, both methods returnfalse.This is separate from
whereExists():exists()executes the current builder to check whether matching rows exist, whilewhereExists()adds an SQLEXISTSpredicate to another query.Changes
BaseBuilder::exists()andBaseBuilder::doesntExist().Checklist: