PHPackages                             thomas-0816/laravel-duckdb - 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. thomas-0816/laravel-duckdb

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

thomas-0816/laravel-duckdb
==========================

DuckDB driver for Laravel Eloquent powered by the DuckDB PDO Driver

1.3(today)04↑2900%MITPHPPHP &gt;=8.2CI passing

Since Jul 23Pushed todayCompare

[ Source](https://github.com/thomas-0816/laravel-duckdb)[ Packagist](https://packagist.org/packages/thomas-0816/laravel-duckdb)[ RSS](/packages/thomas-0816-laravel-duckdb/feed)WikiDiscussions main Synced today

READMEChangelog (5)Dependencies (8)Versions (5)Used By (0)

DuckDB driver for Laravel
=========================

[](#duckdb-driver-for-laravel)

A [DuckDB](https://duckdb.org) database driver for Laravel Eloquent powered by the DuckDB PDO Driver.

Integrates DuckDB's analytical database engine into Laravel's Eloquent ORM and Schema Builder, enabling fast analytical queries directly from your Laravel application.

[![logo](logo.jpg?3)](logo.jpg?3)

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 12+
- pdo\_duckdb PHP extension

Install and setup DuckDB driver for Laravel
-------------------------------------------

[](#install-and-setup-duckdb-driver-for-laravel)

Install and setup [pdo\_duckdb](https://github.com/thomas-0816/pdo-duckdb-php) database driver with [PIE](https://github.com/php/pie):

```
pie install thomas-0816/pdo-duckdb-php
```

Install and setup DuckDB driver for Laravel:

```
composer require thomas-0816/laravel-duckdb

php artisan package:discover
```

`pdo_duckdb` is a native DuckDB database driver for the PHP Data Objects (PDO) interface.
As a native PHP extension, it is implemented in C/C++ and does not require PHP FFI or preloading.
It is also thread safe and fully tested with FrankenPHP (PHP-ZTS).
The release packages contain pre-compiled binaries for all supported platforms and DuckDB is directly included.
DuckDB extensions work the same way as they do in DuckDB CLI.

Configuration
-------------

[](#configuration)

Add a `duckdb` connection to your `config/database.php`:

```
'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
            ],
        ],
    ],
],
```

In-Memory Database
------------------

[](#in-memory-database)

For testing or reading external files, use the special in-memory database in `config/database.php`:

```
'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
            ],
        ],
    ],
],
```

Schema Builder
--------------

[](#schema-builder)

```
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');
```

Query Builder Insert
--------------------

[](#query-builder-insert)

```
use Illuminate\Support\Facades\DB;

DB::connection('duckdb')->table('events')->insert([[
    'category' => 'conference',
    'amount' => 42.21,
    'tags' => ['Hello', 'DuckDB'],
    'created_at' => now(),
    'updated_at' => now(),
]]);
```

Query Builder Select
--------------------

[](#query-builder-select)

```
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')
    ->groupBy('week')
    ->orderBy('week')
    ->get();

dump($result->toArray());
```

Eloquent Models
---------------

[](#eloquent-models)

Models can be directly used by specifying the connection "duckdb":

```
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());
```

Read CSV files with SQL Query Builder
-------------------------------------

[](#read-csv-files-with-sql-query-builder)

```
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();
print_r($result->toArray());

# Array
#     [0] => stdClass Object
#         [aaa] => 123
#     [1] => stdClass Object
#         [aaa] => aaa
```

Read CSV files with Eloquent Models
-----------------------------------

[](#read-csv-files-with-eloquent-models)

```
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
```

Read JSON files with SQL Query Builder
--------------------------------------

[](#read-json-files-with-sql-query-builder)

```
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();
print_r($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();
print_r($result->toArray());

# Array
#     [0] => stdClass Object
#         [log] => log text
#     [1] => stdClass Object
#         [log] => log text 2
```

Read JSON files with Eloquent Models
------------------------------------

[](#read-json-files-with-eloquent-models)

```
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
```

Read PARQUET files with Eloquent Models
---------------------------------------

[](#read-parquet-files-with-eloquent-models)

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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
```

**Apache Parquet**: very fast and efficient column based storage file format containing one table of data.
Each column is split into several column groups. Depending on the query, the file can be read partially by certain columns groups.
Different compression or dictionary algorithms can be applied to each column. Also supports encryption.

Note: You can read and save Parquet files on local file systems or directly on [S3 object storage](https://duckdb.org/docs/lts/core_extensions/httpfs/s3api).

Read and write PARQUET files with SQL Query Builder
---------------------------------------------------

[](#read-and-write-parquet-files-with-sql-query-builder)

```
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();
print_r($result->toArray());

# Array
#     [0] => stdClass Object
#         [id] => 1
#         [text] => Hello DuckDB 🦆
#         [data] => Array
#             [foo] => bar
#             [baz] => 42
```

Read public data using HTTPs, JSON and CSV
------------------------------------------

[](#read-public-data-using-https-json-and-csv)

```
$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}]
```

Copy data from MariaDB to a parquet file
----------------------------------------

[](#copy-data-from-mariadb-to-a-parquet-file)

Start a MariaDB container, create and fill "orders" table:

```
docker run --rm -it -p 3306:3306 -e MARIADB_ROOT_PASSWORD=secret -e MARIADB_DATABASE=testdb mariadb:12
mysql -h 127.0.0.1 -u root -psecret testdb -e "
    CREATE TABLE orders (id integer primary key, customer integer, amount decimal(12, 2), origin varchar(255));
    INSERT INTO orders VALUES (1, 42, 123.42, 'shop');
    INSERT INTO orders VALUES (2, 21, 12.21, 'offline');
"
```

Use DuckDB MySQL extension to copy "orders" table from MariaDB to a parquet file:

```
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();
print_r($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
```

Schema Builder for special types
--------------------------------

[](#schema-builder-for-special-types)

Special types can be defined by using rawColumn():

```
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

Schema::connection('duckdb')->create('employees', function (Blueprint $table) {
    $table->id();
    $table->rawColumn('categories', 'varchar[]');
    $table->rawColumn('numbers', 'integer[]');
    $table->rawColumn('person', 'STRUCT(v VARCHAR, i INTEGER, va VARCHAR[], d DECIMAL)');
});

class Person {
    public string $v;
    public int $i;
    public array $va;
    public float $d;
}

$person = new Person();
$person->v = 'foo';
$person->i = 42;
$person->va = ['foo', 'bar'];
$person->d = 42.21;

DB::connection('duckdb')->table('employees')->insert([[
    'categories' => ['foo', 'bar'],
    'numbers' => [42, 21],
    'person' => $person,
]]);
$employees = $DB::connection('duckdb')->query()
    ->from('employees')
    ->get();
print_r($employees->toArray());

# Array
#     [0] => stdClass Object
#         [id] => 1
#         [categories] => Array
#             [0] => foo
#             [1] => bar
#         [numbers] => Array
#             [0] => 42
#             [1] => 21
#         [person] => Array
#             [v] => foo
#             [i] => 42
#             [va] => Array
#                 [0] => foo
#                 [1] => bar
#             [d] => 42.21
```

Schema Dump
-----------

[](#schema-dump)

The package supports `schema:dump` Artisan command using DuckDB's `EXPORT DATABASE` SQL statement via PDO:

```
php artisan schema:dump --database=duckdb # creates ./database/schema/duckdb-schema.sql
```

Query Debugging
---------------

[](#query-debugging)

You can add this line at the beginning of your script for local query debugging:

```
\Illuminate\Support\Facades\DB::listen(fn ($query) => dump($query));
```

Security
--------

[](#security)

Use SQL `SET variable = value;` or put the settings inside the PDO::DUCKDB\_ATTR\_CONFIG connection [options array](#Configuration):

```
# Disable extension loading
SET autoload_known_extensions = false;
SET autoinstall_known_extensions = false;
SET allow_community_extensions = false;

# Disable external file access, directory white listing
SET allowed_directories = ['/tmp'];
SET enable_external_access = false;

# Resource limits
SET threads = 4;
SET memory_limit = '4GB';
SET max_temp_directory_size = '4GB';
```

A complete list is available in the DuckDB documentation: [Securing DuckDB](https://duckdb.org/docs/lts/operations_manual/securing_duckdb/overview).

Development
-----------

[](#development)

```
# testing
composer test
composer test_fix
./vendor/bin/pest --coverage
```

Why DuckDB?
-----------

[](#why-duckdb)

In-Process Architecture: Like SQLite, DuckDB embeds directly into host applications, eliminating the need for a separate server setup.

Extreme Analytical Speed: It uses columnar storage and vectorized (batch) processing, running analytics 10–100x faster than traditional row-oriented databases.

"Larger-than-Memory" Processing: DuckDB gracefully spills data to disk, allowing you to process massive datasets (e.g., 50GB+) on a machine with minimal RAM (e.g., 1GB).

File-Format Agnostic: It can query flat files (JSON, CSV, and Parquet) directly via SQL without needing to import or load the data into a database first.

No Infrastructure Cost: It brings data warehouse-level performance to your local laptop or local server.

DuckDB achieves blazing-fast analytical performance through its embedded, serverless multi-core architecture combined with columnar storage and vectorized execution. By executing queries directly within the host application, it eliminates serialization and network overhead, processing data in batches (vectors) rather than row-by-row for unparalleled speed.

[https://duckdb.org/why\_duckdb](https://duckdb.org/why_duckdb)

Key Performance Advantages:

Vectorized Query Execution: Unlike row-oriented engines, DuckDB processes data in cache-friendly batches (vectors). This allows modern hardware to operate on entire arrays of data simultaneously, drastically reducing CPU cycles per query.

Columnar Storage: Data is stored by column rather than by row. For analytical queries that only require a few metrics, DuckDB only reads the relevant columns from disk/memory, saving massive amounts of I/O.

Zero-Copy In-Process Engine: As an in-process database, DuckDB runs directly in the memory space of your application.

Advanced Query Optimizer: DuckDB features an advanced query optimizer that handles filter pushdowns, unnesting of subqueries, and dynamic runtime filters. This ensures queries only scan necessary data and avoids full-table sorting when possible.

Direct File Querying: You can query large datasets in open formats like Parquet and CSV directly on disk or in cloud storage (like AWS S3) without needing to import or convert the data first.

AI Disclosure
-------------

[](#ai-disclosure)

The code is written by AI, reviewed and tested without AI.

License
-------

[](#license)

MIT License

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance100

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

4

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b2d76ab33e3380db92d1488fbce154b710a4f0795eb337f46cfc2596b588d41a?d=identicon)[thomas-0816](/maintainers/thomas-0816)

---

Top Contributors

[![thomas-0816](https://avatars.githubusercontent.com/u/941223?v=4)](https://github.com/thomas-0816 "thomas-0816 (187 commits)")

---

Tags

laraveldatabaseanalyticsduckdb

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/thomas-0816-laravel-duckdb/health.svg)

```
[![Health](https://phpackages.com/badges/thomas-0816-laravel-duckdb/health.svg)](https://phpackages.com/packages/thomas-0816-laravel-duckdb)
```

###  Alternatives

[illuminate/queue

The Illuminate Queue package.

20433.0M1.7k](/packages/illuminate-queue)[illuminate/database

The Illuminate Database package.

2.8k55.8M12.5k](/packages/illuminate-database)[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M271](/packages/laravel-ai)[illuminate/view

The Illuminate View package.

13047.7M2.3k](/packages/illuminate-view)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M350](/packages/psalm-plugin-laravel)[illuminate/session

The Illuminate Session package.

9939.9M877](/packages/illuminate-session)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
