the-tinderbox/clickhouse-builder
Install the package:
composer require the-tinderbox/clickhouse-builder
Initialize the builder (requires clickhouse-php-client):
use Tinderbox\Clickhouse\Client;
use Tinderbox\Clickhouse\Server;
use Tinderbox\Clickhouse\ServerProvider;
use Tinderbox\ClickhouseBuilder\Builder;
$server = new Server('127.0.0.1', '8123', 'default', 'user', 'pass');
$serverProvider = (new ServerProvider())->addServer($server);
$client = new Client($serverProvider);
$builder = new Builder($client);
First query (e.g., fetch data from a table):
$results = $builder->select('column1', 'column2')->from('table')->get();
Register the service provider in config/app.php (Laravel) or bootstrap/app.php (Lumen):
\Tinderbox\ClickhouseBuilder\Integrations\Laravel\ClickhouseServiceProvider::class
Configure config/database.php:
'connections' => [
'clickhouse' => [
'driver' => 'clickhouse',
'host' => '127.0.0.1',
'port' => '8123',
'database' => 'default',
'username' => 'user',
'password' => 'pass',
],
]
Use the builder via Laravel's DB facade:
$results = DB::connection('clickhouse')->query()->select('*')->from('table')->get();
Fluent Interface: Chain methods for readability and maintainability:
$results = $builder
->select('user_id', 'name')
->from('users')
->where('active', true)
->orderBy('created_at', 'desc')
->limit(10)
->get();
Subqueries: Use closures or nested builders for complex logic:
$subQuery = $builder->select('order_id')->from('orders')->where('status', 'completed');
$results = $builder
->select('users.*')
->from('users')
->whereIn('id', $subQuery)
->get();
Dynamic Conditions: Build conditions dynamically (e.g., for filtering):
$filters = ['active' => true, 'role' => 'admin'];
$query = $builder->from('users');
foreach ($filters as $field => $value) {
$query->where($field, $value);
}
Aggregations:
$results = $builder
->select('department', raw('count(*) as user_count'))
->from('users')
->groupBy('department')
->get();
Joins:
$results = $builder
->select('users.name', 'orders.total')
->from('users')
->leftJoin('orders', 'all', ['users.id' => 'orders.user_id'])
->get();
Temporary Tables:
use Tinderbox\ClickhouseBuilder\TempTable;
use Tinderbox\Clickhouse\Format;
$builder->addFile(new TempTable('temp_numbers', 'data.tsv', ['id' => 'UInt64'], Format::TSV));
$results = $builder
->select('users.*')
->from('users')
->whereIn('id', 'temp_numbers')
->get();
Async Queries:
$builder->from('table1')->asyncWithQuery(function ($query) {
$query->from('table2');
});
$results = $builder->get(); // Returns array of results for each async query
Query Scoping: Extend the builder for reusable logic:
namespace App\Scopes;
use Tinderbox\ClickhouseBuilder\Builder;
class ActiveScope {
public function apply(Builder $builder) {
return $builder->where('active', true);
}
}
Eloquent-like Usage: Combine with Laravel's Eloquent for hybrid queries:
$query = DB::connection('clickhouse')->query();
$users = User::whereIn('id', function ($q) use ($query) {
$q->select('id')->from('clickhouse_users')->where('active', true);
})->get();
Column Functions:
Column class for complex expressions (e.g., sumIf) is unstable and under development. Avoid relying on it for production-critical logic.$builder->select(function ($column) {
$column->name('time')->sumIf('time', '>', 10); // May break in future versions
});
Async Queries:
asyncWithQuery are returned as an array of results, not a single result set. Ensure your code handles this:
$results = $builder->asyncWithQuery(...)->get();
foreach ($results as $resultSet) {
// Process each result set
}
Temporary Tables:
addFile() before using the temp table in whereIn, join, etc. Otherwise, the table won't be recognized.into_memory_table() helper drops and recreates the table. Use cautiously in production to avoid accidental data loss.Dictionary Functions:
whereDict requires the dictionary to exist in ClickHouse. Test your queries first to avoid runtime errors:
$builder->whereDict('user_roles', 'role', 'admin', '=', 'value');
Raw SQL Injection:
raw() for dynamic SQL, but validate inputs to prevent injection:
$builder->where('column', raw("'value'")); // Safe if 'value' is trusted
Inspect the Query:
Use toSql() to debug the generated SQL:
$sql = $builder->select('*')->from('table')->toSql();
dd($sql); // Dump the SQL for inspection
Error Handling: Wrap queries in try-catch blocks to handle ClickHouse-specific errors:
try {
$results = $builder->select('*')->from('nonexistent_table')->get();
} catch (\Exception $e) {
\Log::error("ClickHouse error: " . $e->getMessage());
}
Performance:
SELECT *: Explicitly list columns to reduce data transfer.LIMIT: Always limit results during development to avoid overwhelming your local machine.Custom Query Macros: Add reusable query logic via macros (Laravel-specific):
Builder::macro('activeUsers', function () {
return $this->where('active', true)->where('role', 'user');
});
Usage:
$builder->activeUsers()->get();
Override Default Behavior:
Extend the Builder class to customize query building:
class CustomBuilder extends Builder {
public function customJoin($table, $columns) {
return $this->join($table, 'all', 'inner', $columns);
}
}
Integrate with Laravel Events:
Listen to query events (e.g., illuminate.query) to log or modify queries:
\DB::listen(function ($query) {
if ($query->connectionName === 'clickhouse') {
\Log::debug("ClickHouse Query: " . $query->sql);
}
});
Cluster vs. Single Server:
servers for clusters or a single host for standalone instances in config/database.php.'servers' => [
['host' => 'ch-00.domain.com', 'port' => '8123'],
['host' => 'ch-01.domain.com', 'port' => '8123'],
]
Protocol:
protocol to https if using secure connections:
'options' => ['protocol' => 'https']
3
How can I help you explore Laravel packages today?