PHP code example of thomas-0816 / laravel-duckdb
1. Go to this page and download the library: Download thomas-0816/laravel-duckdb library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
thomas-0816 / laravel-duckdb example snippets
'connections' => [
'duckdb' => [
'driver' => 'duckdb',
'database' => env('DB_DATABASE', database_path('analytics.duckdb')),
'options' => [
PDO::DUCKDB_ATTR_CONFIG => [
'TimeZone' => 'Europe/Berlin',
// 'threads' => 4, # max. number of threads
// 'memory_limit' => '4GB', # max. memory usage
// 'access_mode' => 'read_only', # open database file read-only
],
],
],
],
'connections' => [
'duckdb' => [
'driver' => 'duckdb',
'database' => ':memory:',
'options' => [
PDO::DUCKDB_ATTR_CONFIG => [
'TimeZone' => 'Europe/Berlin',
// 'threads' => 4, # max. number of threads
// 'memory_limit' => '4GB', # max. memory usage
],
],
],
],
$connection = (new ConnectionFactory(app()))
->make(['driver' => 'duckdb', 'database' => ':memory:', 'duckdb');
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// up
Schema::connection('duckdb')->create('events', function (Blueprint $table) {
$table->id(); // creates sequence "seq_events_id" as auto-increment
$table->string('category');
$table->decimal('amount', 12, 2);
$table->json('tags')->nullable();
$table->timestamps();
});
// php artisan migrate --pretend
// php artisan migrate
// down
// Schema::connection('duckdb')->createSequence('seq_events_id', 1, 1);
Schema::connection('duckdb')->dropSequence('seq_events_id');
Schema::connection('duckdb')->dropIfExists('events');
use Illuminate\Support\Facades\DB;
dump(DB::connection('duckdb')->table('events')->insertGetId([[
'category' => 'conference',
'amount' => 42.21,
'tags' => ['Hello', 'DuckDB'],
'created_at' => now(),
'updated_at' => now(),
]]));
# 1
use Illuminate\Support\Facades\DB;
$result = DB::connection('duckdb')->query()
->selectExpression("date_trunc('week', created_at)", 'week')
->selectExpression('sum(amount)', 'revenue')
->selectExpression('histogram(tags)', 'tags')
->from('events')
->where('amount', '>', 100)
->groupBy('week')
->orderBy('week')
->get();
dump($result->toArray());
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
protected $connection = 'duckdb';
protected $table = 'events';
}
use App\Models\Event;
$event = new Event();
$event->category = 'conference';
$event->amount = 42.21;
$event->tags = ['Hello', 'DuckDB'];
$event->save();
dump($event->toArray());
$events = Event::where('created_at', '>=', now()->subWeek())->get();
dump($events->toArray());
use Illuminate\Support\Facades\DB;
$list = [
['aaa', 'bbb', 'ccc'],
['123', '456', '789'],
['ddd', 'eee', 'fff'],
];
$fp = fopen('/tmp/test.csv', 'w');
foreach ($list as $fields) {
fputcsv($fp, $fields, ',', '"', "");
}
fclose($fp);
$result = DB::connection('duckdb')->query()
->select('aaa')
->from('/tmp/test.csv') // or multiple files using '/tmp/*.csv'
->get();
dump($result->toArray());
# Array
# [0] => stdClass Object
# [aaa] => 123
# [1] => stdClass Object
# [aaa] => aaa
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TestCsv extends Model
{
protected $connection = 'duckdb';
protected $table = '/tmp/test.csv'; // or multiple files using '/tmp/*.csv'
}
use App\Models\TestCsv;
$list = [
['aaa', 'bbb', 'ccc'],
['123', '456', '789'],
];
$fp = fopen('/tmp/test.csv', 'w');
foreach ($list as $fields) {
fputcsv($fp, $fields, ',', '"', "");
}
fclose($fp);
$rows = TestCsv::select('aaa')->get();
dump($rows->toArray());
# Array
# [0] => Array
# [aaa] => 123
# [1] => Array
# [aaa] => ddd
use Illuminate\Support\Facades\DB;
$list = [
['aaa', 'bbb'],
['123', '456'],
['aaa', 'bbb']
];
$fp = fopen('/tmp/test.csv', 'w');
foreach ($list as $fields) {
fputcsv($fp, $fields, ',', '"', "");
}
fclose($fp);
$db = DB::connection('duckdb');
$db->statement("CREATE TABLE test_csv AS SELECT * FROM '/tmp/test.csv'"); // schema + data import
$db->statement("INSERT INTO test_csv SELECT * FROM '/tmp/test.csv'"); // only import data
dump($db->select('SHOW test_csv'));
# Array
# [0] => stdClass Object
# [column_name] => aaa
# [column_type] => VARCHAR
# [null] => YES
# [1] => stdClass Object
# [column_name] => bbb
# [column_type] => VARCHAR
# [null] => YES
use Illuminate\Support\Facades\DB;
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text']) . PHP_EOL, FILE_APPEND);
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text 2']) . PHP_EOL, FILE_APPEND);
$result = DB::connection('duckdb')->query()
->select('log')
->from('/tmp/logs.json') // or multiple files using '/tmp/*.json'
->get();
dump($result->toArray());
# Array
# [0] => stdClass Object
# [log] => log text
# [1] => stdClass Object
# [log] => log text 2
// Convert JSON file to Parquet file
DB::connection('duckdb')->statement("COPY (SELECT * FROM '/tmp/logs.json') TO '/tmp/logs.parquet'");
$result = DB::connection('duckdb')->query()
->select('log')
->from('/tmp/logs.parquet')
->get();
dump($result->toArray());
# Array
# [0] => stdClass Object
# [log] => log text
# [1] => stdClass Object
# [log] => log text 2
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LogsJson extends Model
{
protected $connection = 'duckdb';
protected $table = '/tmp/logs.json'; // or multiple files using '/tmp/*.json'
}
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text']) . PHP_EOL, FILE_APPEND);
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text 2']) . PHP_EOL, FILE_APPEND);
$rows = LogsJson::select('log')->get();
dump($rows->toArray());
# Array
# [0] => Array
# [log] => log text
# [1] => Array
# [log] => log text 2
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class LogsParquet extends Model
{
protected $connection = 'duckdb';
protected $table = '/tmp/logs.parquet'; // or multiple files using '/tmp/*.parquet'
}
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text']) . PHP_EOL, FILE_APPEND);
file_put_contents('/tmp/logs.json', json_encode(['log' => 'log text 2']) . PHP_EOL, FILE_APPEND);
// Convert JSON file to Parquet file
DB::connection('duckdb')->statement("COPY (SELECT * FROM '/tmp/logs.json') TO '/tmp/logs.parquet'");
$rows = LogsParquet::select('log')->get();
dump($rows->toArray());
# Array
# [0] => Array
# [log] => log text
# [1] => Array
# [log] => log text 2
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
Schema::connection('duckdb')->create('table1', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->json('data');
});
DB::connection('duckdb')->table('table1')->insert([[
'text' => 'Hello DuckDB 🦆',
'data' => ['foo' => 'bar', 'baz' => 42],
]]);
DB::connection('duckdb')->statement("COPY (SELECT * FROM table1) TO '/tmp/table1.parquet'");
$result = DB::connection('duckdb')->query()
->from('/tmp/table1.parquet')
->get();
dump($result->toArray());
# Array
# [0] => stdClass Object
# [id] => 1
# [text] => Hello DuckDB 🦆
# [data] => Array
# [foo] => bar
# [baz] => 42
use Illuminate\Support\Facades\DB;
DB::connection('duckdb')->unprepared('INSTALL excel; LOAD excel');
DB::connection('duckdb')->unprepared('CREATE TABLE table1 (text VARCHAR, amount DECIMAL(10, 2))');
DB::connection('duckdb')->table('table1')->insert([
['text' => 'Hello Excel 🦆', 'amount' => 42.21],
]);
DB::connection('duckdb')->unprepared("COPY (SELECT * FROM table1) TO '/tmp/table1.xlsx'");
DB::connection('duckdb')->query()
->from('/tmp/table1.xlsx')
->get()
dump($result->toArray());
# Array
# [A1] => Hello DuckDB 🦆
# [B1] => 42.21
use Illuminate\Support\Facades\DB;
$result = DB::connection('duckdb')->query()
->select(['id', 'name.en'])
->fromRaw("read_json('https://bulk.meteostat.net/v2/stations/lite.json.gz')")
->whereLike('name.en', '%Berlin%')
->limit(2);
echo json_encode($result->get()->toArray()), PHP_EOL;
# [{"id":"10381","en":"Berlin \/ Dahlem"},{"id":"10382","en":"Berlin \/ Tegel"}]
$result = DB::connection('duckdb')->query()
->select(['hour', 'temp'])
->fromRaw("read_csv('https://data.meteostat.net/hourly/2026/10381.csv.gz')")
->where('year', 2026)->where('month', 7)->where('day', 25)->where('hour', '>', 9)
->limit(3);
echo json_encode($result->get()->toArray()), PHP_EOL;
# [{"hour":10,"temp":24.1},{"hour":11,"temp":25.5},{"hour":12,"temp":26.4}]
# wget https://huggingface.co/datasets/piebro/deutsche-bahn-data/resolve/main/monthly_processed_data/data-2026-07.parquet
$rows = DB::connection('duckdb')->select("
SELECT train_type, train_number, round(avg(delay_in_min)) as delay_avg, count(*) as count
FROM 'data-2026-07.parquet' WHERE train_type = 'ICE'
GROUP BY train_number, train_type
ORDER BY delay_avg DESC
LIMIT 10
");
dump(array_map('json_encode', $rows));
$rows = DB::connection('duckdb')->select("
SELECT train_number, station_name, delay_in_min, hour(time) as hour, departure_is_canceled
FROM 'data-2026-07.parquet'
WHERE train_number = 647 AND time::date = '2026-07-11'
");
dump(array_map('json_encode', $rows));
# array
# {"train_type":"ICE","train_number":"647","delay_avg":70,"count":35}
# {"train_type":"ICE","train_number":"1541","delay_avg":66,"count":198}
# {"train_type":"ICE","train_number":"79152","delay_avg":44,"count":2}
# {"train_type":"ICE","train_number":"2587","delay_avg":44,"count":72}
...
# array
# {"train_number":"647","station_name":"Dortmund Hbf","delay_in_min":82,"hour":0,"departure_is_canceled":false}
# {"train_number":"647","station_name":"Hamm (Westf) Hbf","delay_in_min":120,"hour":1,"departure_is_canceled":false}
# {"train_number":"647","station_name":"Bielefeld Hbf","delay_in_min":120,"hour":1,"departure_is_canceled":false}
# {"train_number":"647","station_name":"Minden (Westf)","delay_in_min":123,"hour":2,"departure_is_canceled":false}
...
use Illuminate\Support\Facades\DB;
$url = 'https://httpbin.org/headers';
DB::connection('duckdb')->unprepared("CREATE SECRET http_auth (TYPE http, SCOPE '{$url}', BEARER_TOKEN 'some secret')");
$result = DB::connection('duckdb')->query()
->select('*')
->fromRaw("read_json('{$url}?foo=bar')")
->get();
var_dump($result[0]);
# stdClass Object
# [headers] => Array
# [Accept] => */*
# [Authorization] => Bearer some secret
use Illuminate\Support\Facades\DB;
// set credentials
DB::connection('duckdb')->unprepared("
CREATE SECRET rustfs (TYPE s3, ENDPOINT '127.0.0.1:9000', USE_SSL false,
URL_STYLE 'path', KEY_ID 'access-key', SECRET 'secret-key')");
// write data to S3
DB::connection('duckdb')->unprepared("
COPY ( select 'bar' as foo ) TO 's3://my-bucket/numbers.parquet' (FORMAT parquet)");
// read data from S3
$result = DB::connection('duckdb')->query()
->select('*')
->fromRaw("'s3://my-bucket/numbers.parquet'")
->get();
var_dump($result[0]);
# stdClass Object
# [foo] => bar
use Illuminate\Support\Facades\DB;
DB::connection('duckdb')->unprepared("
INSTALL mysql;
ATTACH 'host=127.0.0.1 port=3306 user=root password=secret database=testdb' AS testdb (TYPE mysql);
COPY (select * from testdb.orders) TO '/tmp/orders.parquet' (FORMAT parquet);
");
$rows = DB::connection('duckdb')->query()
->from('/tmp/orders.parquet')
->get();
dump($rows->toArray());
# Array
# [0] => stdClass Object
# [id] => 1
# [customerId] => 42
# [amount] => 123.42
# [origin] => shop
# [1] => stdClass Object
# [id] => 2
# [customerId] => 21
# [amount] => 12.21
# [origin] => offline
use Illuminate\Support\Facades\DB;
DB::connection('duckdb')->unprepared("
INSTALL postgres;
ATTACH 'host=127.0.0.1 port=5432 user=postgres password=secret' AS testdb (TYPE postgres);
COPY (select * from testdb.orders) TO '/tmp/orders.parquet' (FORMAT parquet);
");
$rows = DB::connection('duckdb')->query()
->from('/tmp/orders.parquet')
->get();
dump($rows->toArray());
# Array
# [0] => Array
# [id] => 1
# [customerId] => 42
# [amount] => 123.42
# [origin] => shop
# [1] => Array
# [id] => 2
# [customerId] => 21
# [amount] => 12.21
# [origin] => offline
$data = [];
for ($i = 0; $i < 1_000_000; $i++) {
$data[] = ['i1' => $i, 'v1' => 'foo' . $i];
}
DB::connection('duckdb')->unprepared('CREATE TABLE t1 (i1 integer, v1 varchar)');
DB::connection('duckdb')->unprepared("INSERT INTO t1 SELECT value->>'i1', value->>'v1' FROM json_each('" . json_encode($data) . "')");
$result = DB::connection('duckdb')->query()
->selectExpression('count(*)', 'count')
->from('t1')
->first();
dump($result->count); # 1000000
$data = [];
for ($i = 0; $i < 1_000_000; $i++) {
$data[] = [$i, 'foo' . $i];
}
DB::connection('duckdb')->unprepared('CREATE TABLE t1 (i1 integer, v1 varchar)');
DB::connection('duckdb')->unprepared("INSERT INTO t1 SELECT value->>0, value->>1 FROM json_each('" . json_encode($data) . "')");
$result = DB::connection('duckdb')->query()
->selectExpression('count(*)', 'count')
->from('t1')
->first();
dump($result->count); # 1000000
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
Schema::connection('duckdb')->create('events', function (Blueprint $table) {
$table->id();
$table->rawColumn('numbers', 'integer[]');
$table->rawColumn('categories', 'varchar[]');
$table->rawColumn('person', 'STRUCT(v VARCHAR, va VARCHAR[], d DECIMAL)');
});
class Person {
public string $v;
public array $va;
public float $d;
}
$person = new Person();
$person->v = 'foo';
$person->va = ['bar', 'baz'];
$person->d = 12.34;
DB::connection('duckdb')->table('events')->insert([[
'numbers' => [21, 42],
'categories' => ['cat1', 'cat2'],
'person' => $person,
]]);
$events = DB::connection('duckdb')->query()
->from('events')
->get();
dump($events->toArray());
# Array
# [0] => stdClass Object
# [id] => 1
# [numbers] => Array
# [0] => 21
# [1] => 42
# [categories] => Array
# [0] => cat1
# [1] => cat2
# [person] => Array
# [v] => foo
# [va] => Array
# [0] => bar
# [1] => baz
# [d] => 12.34
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::connection('duckdb')->create('events', function (Blueprint $table) {
$table->id();
$table->rawColumn('numbers', 'integer[]');
$table->rawColumn('categories', 'varchar[]');
$table->rawColumn('person', 'STRUCT(v VARCHAR, va VARCHAR[], d DECIMAL)');
$table->rawColumn('persons', 'STRUCT(v VARCHAR, va VARCHAR[], d DECIMAL)[]');
$table->timestamps();
});
class Person extends Model
{
protected $guarded = [];
}
class Event extends Model
{
protected $connection = 'duckdb';
protected $table = 'events';
protected function person(): Attribute
{
return Attribute::get(fn ($person) => new Person($person));
}
protected function persons(): Attribute
{
return Attribute::get(fn ($persons) => collect($persons)->map(fn ($values) => new Person($values)));
}
}
$person = new Person();
$person->v = 'foo';
$person->va = ['bar', 'baz'];
$person->d = 12.34;
$event = new Event();
$event->numbers = [21, 42];
$event->categories = ['cat1', 'cat2'];
$event->person = $person;
$event->persons = [$person];
$event->save();
dump(Event::first()->toArray());
# Array
# [id] => 1
# [numbers] => Array
# [0] => 42
# [1] => 21
# [categories] => Array
# [0] => cat1
# [1] => cat2
# [person] => Person Object
# [v] => foo
# [va] => Array
# [0] => bar
# [1] => baz
# [d] => 42.21
# [persons] => Array
# [0] => Array
# [v] => foo
# [va] => Array
# [0] => bar
# [1] => baz
# [d] => 42.21
DB::connection('duckdb')->unprepared('INSTALL vss; LOAD vss');
Schema::connection('duckdb')->create('events', function (Blueprint $table) {
$table->id();
$table->vector('embeddings', 3);
$table->vectorIndex('embeddings');
});
DB::connection('duckdb')->table('events')->insert([
['id' => 1, 'embeddings' => [1, 2, 3]],
['id' => 2, 'embeddings' => [4, 5, 6]],
]);
$results = DB::connection('duckdb')->table('events')
->select('id')
->selectVectorDistance('embeddings', [1.1, 2.1, 3.1], 'distance')
->where('distance', '<=', 0.01)
->orderBy('distance')
->get();
dump($results->toArray());
# Array
# [0] => stdClass Object
# [id] => 1
# [distance] => 0.0001407862\n
use Illuminate\Support\Facades\Schema;
// create or drop views
Schema::connection('duckdb')->createView('view1', 'SELECT * FROM events');
Schema::connection('duckdb')->dropView('view1');
use Illuminate\Support\Facades\DB;
$list = [
['aaa', 'bbb', 'ccc'],
['123', '456', '789'],
['ddd', 'eee', 'fff'],
];
$fp = fopen('/tmp/test.csv', 'w');
foreach ($list as $fields) {
fputcsv($fp, $fields, ',', '"', "");
}
fclose($fp);
DB::connection('duckdb')->transaction(function ($db) {
$db->statement("CREATE TABLE test_csv AS SELECT * FROM '/tmp/test.csv'");
$db->statement("INSERT INTO test_csv SELECT * FROM '/tmp/test.csv'");
});
'connections' => [
'duckdb' => [
'driver' => 'duckdb',
'database' => ':memory:',
'options' => [
PDO::DUCKDB_ATTR_INIT_COMMAND => "ATTACH 'quack:127.0.0.1:9494' AS remote (TOKEN 'secret'); USE remote;",
]
use Illuminate\Support\Facades\DB;
// open quack connection on demand
// DB::connection('duckdb')->unprepared("ATTACH 'quack:127.0.0.1:9494' AS remote (TOKEN 'secret'); USE remote;");
DB::connection('duckdb')->unprepared('CREATE TABLE IF NOT EXISTS table1 (v VARCHAR, v2 VARCHAR)');
DB::connection('duckdb')->unprepared("INSERT INTO table1 VALUES ('foo', 'bar')");
$result = DB::connection('duckdb')->select('SELECT * FROM table1');
dump($result);
# Array
# [0] => stdClass Object
# [v] => foo
# [v2] => bar
# start llama.cpp at 127.0.0.1:8080
# ./llama-server -hf JetBrains/Mellum2-12B-A2.5B-Thinking-GGUF-Q4_K_M --parallel 1 --ctx-size 16384 --temp 0.6 --top-k 20 --reasoning off
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
DB::connection('duckdb')->unprepared("
INSTALL open_prompt FROM community;
LOAD open_prompt;
SET VARIABLE openprompt_api_url = 'http://127.0.0.1:8080/v1/chat/completions';
");
Schema::connection('duckdb')->create('customers', function (Blueprint $table) {
$table->id();
$table->string('first_name');
$table->string('last_name');
$table->date('birth_date');
});
$result = DB::connection('duckdb')->query()
->selectExpression("open_prompt('write duckdb sql, no markdown, find customers older than 30, schema: ' || group_concat(sql))", 'llm')
->fromRaw('duckdb_tables()')
->first();
# SELECT * FROM customers WHERE birth_date < now() - INTERVAL '30 years';
$result = Octane::concurrently([
fn () => DB::connection('duckdb')->selectOne("select sleep_ms(1000)"),
fn () => DB::connection('duckdb')->selectOne("select sleep_ms(1000)"),
fn () => DB::connection('duckdb')->selectOne("select sleep_ms(1000)"),
]); // takes 1s
class AppServiceProvider extends ServiceProvider {
public function boot(): void {
Octane::tick('my-ticker', function () {
$result = DB::connection('duckdb')->selectOne("select sleep_ms(1000)");
dump($result);
})->seconds(10);
'connections' => [
'duckdb' => [
'driver' => 'duckdb',
'database' => ':memory:',
# or
'connections' => [
'duckdb' => [
'driver' => 'duckdb',
'database' => env('DB_DATABASE', database_path('analytics.duckdb')),
'options' => [PDO::DUCKDB_ATTR_CONFIG => ['access_mode' => 'read_only'],
'listeners' => [
OperationTerminated::class => [
DisconnectFromDatabases::class,
bash
pie install thomas-0816/pdo-duckdb-php
bash
pie install --skip-enable-extension thomas-0816/pdo-duckdb-php
php -d extension=pdo_duckdb artisan some_command
bash
php artisan schema:dump --database=duckdb # creates ./database/schema/duckdb-schema.sql