Skip to content

feat: add Query Builder exists and doesntExist methods#10215

Open
memleakd wants to merge 3 commits into
codeigniter4:4.8from
memleakd:feat/exists-query-builder
Open

feat: add Query Builder exists and doesntExist methods#10215
memleakd wants to merge 3 commits into
codeigniter4:4.8from
memleakd:feat/exists-query-builder

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

Description

This PR proposes adding exists() and doesntExist() 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:

$builder->where('status', 'pending');

if ($builder->exists()) {
    // At least one pending row exists.
}

The implementation compiles a lightweight existence probe, preserves the builder state when $reset is false, 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 return false.

This is separate from whereExists(): exists() executes the current builder to check whether matching rows exist, while whereExists() adds an SQL EXISTS predicate to another query.

Changes

  • Added BaseBuilder::exists() and BaseBuilder::doesntExist().
  • Added Model PHPDoc entries for the new builder methods.
  • Added user guide docs, class reference entries, and changelog entry.
  • Added focused builder and live database tests.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

- 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>
@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label May 18, 2026
@memleakd memleakd changed the title feat(database): add query builder existence checks feat: add Query Builder exists and doesntExist methods May 18, 2026
Comment thread system/Database/BaseBuilder.php Outdated
Comment thread system/Database/BaseBuilder.php
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Comment thread system/Model.php Outdated
Comment thread system/Database/BaseBuilder.php
*
* @return bool|string SQL string when test mode is enabled.
*/
public function exists(bool $reset = true)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread user_guide_src/source/database/query_builder.rst Outdated
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants