Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Clickhouse Builder Laravel Package

esazykin/clickhouse-builder

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin

composer require esazykin/clickhouse-builder

The last tagged release is from December 2017, so before you commit: open composer.json in the package and check its illuminate/support constraint against your Laravel version, and check the PHP constraint against your runtime. If you're on Laravel 8+/PHP 8, expect to either patch a fork or add a maintained alternative behind your own interface (see Gotchas). Treat composer.lock as load-bearing once it works.

2. Register and configure the connection

On Laravel <5.5 (or with package discovery disabled) add the provider and facade alias manually — copy the exact class names from the package's composer.json extra.laravel block rather than guessing. Then publish the config and set the connection:

// config/clickhouse.php (typical shape)
'default' => [
    'host'     => env('CLICKHOUSE_HOST', '127.0.0.1'),
    'port'     => env('CLICKHOUSE_PORT', 8123),   // 8123 = HTTP, 9000 = native
    'database' => env('CLICKHOUSE_DB', 'default'),
    'username' => env('CLICKHOUSE_USER', 'default'),
    'password' => env('CLICKHOUSE_PASSWORD', ''),
],

ClickHouse runs over its own TCP/HTTP protocol — this is not a MySQL connection, so don't point it at a MySQL driver or reuse your app DB credentials.

3. First query — where to look first

Open the package's src/ and read the builder class top to bottom: the public method list is your API surface, and the grammar/compiler class is where SQL is assembled. That ten-minute read tells you exactly which clauses are supported.

use ClickHouse;

$rows = ClickHouse::select('event', 'count() as total')
    ->from('events')
    ->where('date', '>=', '2024-01-01')
    ->groupBy('event')
    ->orderBy('total', 'desc')
    ->limit(10)
    ->get();

Best first use case: a read-only reporting endpoint. It exercises select/where/groupBy/orderBy/limit, gives you immediate feedback, and can't corrupt anything.

Implementation Patterns

Wrap it in a repository, not in controllers. Return plain arrays or collections — there is no Eloquent model here, no relations, no lazy loading. One class per domain query keeps the ClickHouse dialect (and the abandoned dependency) out of your application code.

final class EventReportRepository
{
    public function topEvents(Carbon $from, int $limit = 10): array
    {
        return ClickHouse::from('events')
            ->where('date', '>=', $from->toDateString())
            ->select('event', 'count() as total')
            ->groupBy('event')
            ->orderBy('total', 'desc')
            ->limit($limit)
            ->get();
    }
}

Push aggregation into ClickHouse, not PHP. The win over MySQL is the function library; use raw selects for it:

->selectRaw('countIf(status >= 500) as errors')
->selectRaw('quantile(0.95)(duration_ms) as p95')
->selectRaw('toStartOfHour(created_at) as bucket')

Build filters dynamically, but whitelist columns. A filter map keeps queries readable; never let a request key become a column name.

$query = ClickHouse::from('events');
$allowed = ['event', 'country', 'status'];

foreach ($filters as $column => $value) {
    if (in_array($column, $allowed, true)) {
        $query->where($column, $value);
    }
}

Log the SQL before you run it. toSql() / bindings are your only cheap debugger; expensive ClickHouse queries are expensive to discover by accident in production.

Batch inserts, chunked. Rows must be uniform assoc arrays with identical keys; format DateTime as 'Y-m-d H:i:s' strings, not objects. Chunk to roughly 10k–100k rows per call and insert against a Buffer or Null-engine table when ingestion volume spikes.

foreach (array_chunk($rows, 50000) as $chunk) {
    ClickHouse::table('events')->insert($chunk);
}

Hydrate Eloquent from IDs, not joins. Query ClickHouse for aggregate/ID results, then User::whereIn('id', $ids) in MySQL. Cross-store joins are fragile and ClickHouse's join semantics are not MySQL's.

Keep DDL in versioned .sql files plus an Artisan bootstrap command (createTable, dropTable, truncate exist) rather than in migrations. ClickHouse has no transactions, so "rollback" is a lie you don't want in a migration.

Gotchas and Tips

  • It is effectively unmaintained. Last release 2017, zero dependents. Wrap it behind an interface from day one so replacing it with a maintained fork (several exist, notably Tinderbox's) or with direct client calls is a one-file change. Pin the version; don't let composer update surprise you.
  • Raw means raw. selectRaw/whereRaw concatenate strings — there are no placeholder bindings for raw fragments. Interpolating user input is a SQL injection. Keep all raw fragments as literals in your code.
  • No transactions, and no classic UPDATE/DELETE. ALTER TABLE ... UPDATE/DELETE is an asynchronous mutation, not an interactive statement; don't build UI flows that assume read-after-write.
  • Numbers often come back as strings from the underlying driver. Cast aggressively ((int), (float)) at the repository boundary instead of sprinkling casts in views.
  • Replication lag. With ReplicatedMergeTree, a read immediately after a write may not see it. Design dashboards for eventual consistency; use FINAL sparingly — it's correct but slow.
  • MergeTree requires ORDER BY and does not enforce uniqueness. Dedupe before insert, or use ReplacingMergeTree and accept that deduplication happens at merge time.
  • Timeouts and memory. Set max_execution_time / max_memory_usage per-query where the builder exposes settings, and set the HTTP client timeout higher than your slowest report. Long LIMIT n OFFSET m scans are painful — paginate by keyset instead.
  • Verify bindings, not just SQL. Run toSql() and inspect the bindings array together; mismatch between ? count and bindings is the classic silent failure in this style of builder.
  • Config caching. After changing config/clickhouse.php, run php artisan config:cache or clear it — a stale cache looks exactly like a wrong host.
  • Facade collisions. If any other package in the project aliases ClickHouse, you'll get confusing resolution. Alias it explicitly in config/app.php.
  • Testing. There's no in-memory ClickHouse; run integration tests against a Docker clickhouse-server and assert on toSql() output for unit tests. Anything else is testing your mock.
  • Extension points. Subclassing the builder (or adding macros, if the version supports them) is the cleanest way to add clauses like PREWHERE/SAMPLE; the grammar class is where the SQL actually gets emitted.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource