PHPackages                             rcalicdan/query-builder-primitives - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. rcalicdan/query-builder-primitives

ActiveLibrary[Database &amp; ORM](/categories/database)

rcalicdan/query-builder-primitives
==================================

Immutable querybuilder building block for creating sql query builder libraries

1.0.0(3w ago)1208MITPHPPHP ^8.2CI passing

Since Jun 8Pushed 3w agoCompare

[ Source](https://github.com/rcalicdan/query-builder-primitives)[ Packagist](https://packagist.org/packages/rcalicdan/query-builder-primitives)[ RSS](/packages/rcalicdan-query-builder-primitives/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (6)Versions (2)Used By (0)

Query Builder Primitives
========================

[](#query-builder-primitives)

A collection of PHP traits for building immutable, fluent query builders. This library provides low-level primitives without forcing any specific implementation.

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Philosophy](#philosophy)
- [Supported Database Drivers](#supported-database-drivers)
- [Architecture Overview](#architecture-overview)
    - [Dependency Map](#dependency-map)
    - [Trait Descriptions](#trait-descriptions)
    - [Interfaces](#interfaces)
- [Quick Start](#quick-start)
    - [Minimal Query Builder](#minimal-query-builder)
    - [Full-Featured Query Builder](#full-featured-query-builder)
- [Trait Details](#trait-details)
    - [QueryBuilderCore](#querybuildercore)
        - [The newQuery() Method](#the-newquery-method--required-override-for-custom-constructors)
    - [QueryConditions](#queryconditions)
    - [QueryAdvancedConditions](#queryadvancedconditions)
    - [QueryConditionable](#queryconditionable)
    - [QueryJoin](#queryjoin)
    - [QueryGrouping](#querygrouping)
    - [QueryLocking](#querylocking)
    - [QueryUnion](#queryunion)
    - [QueryCte](#querycte)
    - [QueryJson](#queryjson)
    - [QueryDebug](#querydebug)
    - [SqlBuilder](#sqlbuilder)
        - [buildExistsQuery()](#buildexistsquery)
        - [buildIncrementQuery()](#buildincrementquery)
        - [buildDecrementQuery()](#builddecrementquery)
        - [buildInsertIgnoreQuery()](#buildinsertignorequery)
- [Immutability](#immutability)
- [Extending with Execution](#extending-with-execution)
- [Recommended Compositions](#recommended-compositions)
    - [1. Read-Only Query Builder](#1-read-only-query-builder)
    - [2. Simple Query Builder](#2-simple-query-builder-no-advanced-features)
    - [3. Reporting Query Builder](#3-reporting-query-builder-heavy-on-joingrouping)
    - [4. Full-Featured](#4-full-featured-all-traits)
- [Common Patterns](#common-patterns)
    - [Complex WHERE Logic](#complex-where-logic)
    - [Conditional Query Building](#conditional-query-building)
    - [OR HAVING](#or-having)
    - [Subquery Patterns](#subquery-patterns)
    - [Existence Checks](#existence-checks)
    - [Atomic Counters](#atomic-counters)
    - [Insert Ignore](#insert-ignore)
    - [CTE Patterns](#cte-patterns)
    - [JSON Patterns](#json-patterns)
    - [Pessimistic Locking Patterns](#pessimistic-locking-patterns)
    - [UNION Patterns](#union-patterns)
    - [Reporting Queries](#reporting-queries)
- [Requirements](#requirements)
- [License](#license)
- [Contributing](#contributing)

---

Installation
------------

[](#installation)

```
composer require rcalicdan/query-builder-primitives
```

Philosophy
----------

[](#philosophy)

This library provides **building blocks**, not a complete query builder. You compose the traits you need to create your own custom query builder implementation.

Queries are built through **immutable method chaining** — every method returns a new instance rather than mutating the current one. This is a deliberate design choice with real practical benefits:

- **Safe reuse.** A base query can be forked into multiple independent queries without any of them affecting each other. Build a `$base` once, branch it as many times as you need.
- **No hidden state.** In a mutable builder, calling methods on a shared instance from different parts of your code produces unpredictable results. With immutable chaining, each chain is self-contained and its SQL is exactly what you wrote.
- **Composable defaults.** You can define a pre-configured query (scoped to a tenant, filtered by status, ordered by default) and pass it around freely, knowing no callee can corrupt it.
- **Easier debugging.** Because each step produces a discrete value, you can call `.halt()` or `.toSql()` at any point in the chain without affecting the final query.
- **Safe for asynchronous execution.** When multiple coroutines or fibers execute concurrently and share a mutable builder, one coroutine's `where()` call can bleed into another's query mid-flight — a race condition that is silent, non-deterministic, and extremely difficult to reproduce. Because every method on this builder returns a new independent instance, each coroutine or fiber holds its own copy of the query state from the moment it branches off. There is no shared mutable object to race on, so concurrent query construction is safe by construction rather than by discipline.

```
// Mutable builders share state — this is the problem immutability solves:
$base->where('status', 'active');   // mutates $base
$base->where('role', 'admin');      // mutates $base again — now both conditions are on it

// Immutable chaining — each call returns a new instance, $base is never touched:
$active = $base->where('status', 'active');
$admins = $base->where('role', 'admin');

// $base, $active, and $admins are three completely independent queries

// Safe concurrent use — each fiber/coroutine gets its own query state:
$base = $qb->from('orders')->where('status', 'pending');

$fiber1 = new Fiber(function() use ($base) {
    // Branches off $base into a new instance — completely isolated
    $query = $base->where('user_id', 1)->latest();
    // ... execute $query
});

$fiber2 = new Fiber(function() use ($base) {
    // Also branches off $base — no interference with fiber1
    $query = $base->where('user_id', 2)->oldest();
    // ... execute $query
});

// Both fibers work from the same $base without any risk of one
// overwriting the other's conditions, regardless of execution order
```

The trade-off is that you must always assign or chain the return value — discarding it silently does nothing. This is intentional: the builder never surprises you with side effects.

Supported Database Drivers
--------------------------

[](#supported-database-drivers)

- MySQL/MariaDB
- PostgreSQL
- SQLite

---

Architecture Overview
---------------------

[](#architecture-overview)

### Dependency Map

[](#dependency-map)

```
QueryBuilderCore (foundation - required)
  ↓
SqlBuilder (depends on: properties from condition/join/grouping/cte traits)
  ↓
QueryConditions (depends on: QueryBuilderCore)
  ↓
QueryAdvancedConditions (depends on: QueryConditions, SqlBuilder)

QueryConditionable (depends on: QueryBuilderCore)
QueryJoin (depends on: QueryBuilderCore, JoinClause)
QueryGrouping (depends on: QueryBuilderCore)
QueryLocking (depends on: QueryBuilderCore, SqlBuilder)
QueryUnion (depends on: QueryBuilderCore, SqlBuilder)
QueryCte (depends on: QueryBuilderCore, SqlBuilder)
QueryJson (depends on: QueryBuilderCore, QueryConditions)
QueryDebug (depends on: all traits)

```

### Trait Descriptions

[](#trait-descriptions)

TraitPurposeDependencies`QueryBuilderCore`Core properties, select, and `from()`None (foundation)`SqlBuilder`Builds SQL query stringsQueryBuilderCore + condition/join/grouping/cte traits`QueryConditions`Basic WHERE, HAVING, LIKE clausesQueryBuilderCore`QueryAdvancedConditions`Nested conditions, EXISTS, subqueriesQueryConditions, SqlBuilder`QueryConditionable`Conditional `when()` / `unless()` helpersQueryBuilderCore`QueryJoin`JOIN operations (INNER, LEFT, RIGHT, CROSS) with simple string or advanced closure conditionsQueryBuilderCore, JoinClause`QueryGrouping`GROUP BY, ORDER BY, LIMIT, OFFSET, random order, reorderQueryBuilderCore`QueryLocking`Pessimistic locking (FOR UPDATE, FOR SHARE, NOWAIT, SKIP LOCKED)QueryBuilderCore, SqlBuilder`QueryUnion`UNION and UNION ALL operationsQueryBuilderCore, SqlBuilder`QueryCte`Common Table Expressions (WITH / WITH RECURSIVE)QueryBuilderCore, SqlBuilder`QueryJson`JSON path conditions, contains, length, driver-aware compilationQueryBuilderCore, QueryConditions`QueryDebug`Debug utilities (toSql, dump, dd)All traits### Interfaces

[](#interfaces)

Each trait has a corresponding contract under `Rcalicdan\QueryBuilderPrimitives\Interfaces\`:

InterfaceCovers`CoreInterface``from`, `select`, `addSelect`, `selectRaw`, `selectDistinct``ConditionInterface`All WHERE, HAVING, LIKE methods`AdvancedConditionInterface``whereGroup`, `whereExists`, `whereSub`, etc.`ConditionalInterface``when`, `unless``JoinInterface`All JOIN methods (string and closure conditions)`JoinClauseInterface``on`, `orOn` for advanced closure-based joins`GroupingInterface``groupBy`, `groupByRaw`, `orderBy`, `orderByRaw`, `latest`, `oldest`, `limit`, `offset`, `forPage`, `inRandomOrder`, `reorder``LockingInterface`All locking methods`UnionInterface``union`, `unionAll``CteInterface``with``JsonConditionInterface``whereJson`, `whereJsonContains`, `whereJsonLength`, `whereJsonDoesntContain`, and OR variants`DebugInterface``toSql`, `getBindings`, `toRawSql`, `dump`, `dd``QueryBuilderPrimitiveInterface`Extends all of the above`QueryBuilderBase` implements `QueryBuilderPrimitiveInterface` and uses all traits, making it a ready-made full implementation you can extend.

---

Quick Start
-----------

[](#quick-start)

### Minimal Query Builder

[](#minimal-query-builder)

```
