foundry-co / laravel-snowflake
Snowflake database driver for Laravel using REST API - no PDO extension required
v0.1.3
2026-02-13 17:30 UTC
Requires
- php: ^8.3
- firebase/php-jwt: ^6.10
- laravel/framework: ^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
This package is auto-updated.
Last update: 2026-03-13 17:38:05 UTC
README
A Laravel database driver for Snowflake using the REST SQL API. No PHP extensions or ODBC drivers required.
Features
- Pure PHP implementation using Snowflake's REST API
- Full Eloquent support with models and relationships
- Laravel Query Builder with Snowflake-specific SQL
- Migrations with Snowflake-specific column types
- ULID primary keys optimized for Snowflake clustering
- Native support for VARIANT, OBJECT, and ARRAY types
- Large result set streaming via partitions
Requirements
- PHP 8.2+
- Laravel 12.0+
- Snowflake account with REST API access
Installation
composer require foundry-co/laravel-snowflake
The package will auto-register its service provider.
Configuration
1. Snowflake Account Setup
Set up key-pair authentication in Snowflake:
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out snowflake_key.p8 -nocrypt
openssl rsa -in snowflake_key.p8 -pubout -out snowflake_key.pub
Assign the public key to your Snowflake user:
ALTER USER your_user SET RSA_PUBLIC_KEY='MIIBIjANBgkqh...';
2. Environment Variables
SNOWFLAKE_ACCOUNT=your-account-identifier
SNOWFLAKE_WAREHOUSE=COMPUTE_WH
SNOWFLAKE_DATABASE=MY_DATABASE
SNOWFLAKE_SCHEMA=PUBLIC
SNOWFLAKE_USER=your_username
SNOWFLAKE_PRIVATE_KEY_PATH=/path/to/snowflake_key.p8
SNOWFLAKE_ROLE=SYSADMIN
3. Database Configuration
Add the Snowflake connection to config/database.php:
'connections' => [
'snowflake' => [
'driver' => 'snowflake',
'account' => env('SNOWFLAKE_ACCOUNT'),
'warehouse' => env('SNOWFLAKE_WAREHOUSE'),
'database' => env('SNOWFLAKE_DATABASE'),
'schema' => env('SNOWFLAKE_SCHEMA', 'PUBLIC'),
'role' => env('SNOWFLAKE_ROLE'),
'auth' => [
'jwt' => [
'user' => env('SNOWFLAKE_USER'),
'private_key_path' => env('SNOWFLAKE_PRIVATE_KEY_PATH'),
'private_key_passphrase' => env('SNOWFLAKE_PRIVATE_KEY_PASSPHRASE'),
],
],
],
],
You can also provide the private key content directly instead of a file path:
'auth' => [
'jwt' => [
'user' => env('SNOWFLAKE_USER'),
'private_key' => env('SNOWFLAKE_PRIVATE_KEY'),
],
],
Usage
Eloquent Models
Add the UsesSnowflake trait to any model that connects to Snowflake:
use Illuminate\Database\Eloquent\Model;
use FoundryCo\Snowflake\Eloquent\Concerns\UsesSnowflake;
class User extends Model
{
use UsesSnowflake;
protected $connection = 'snowflake';
protected $table = 'users';
}
The trait automatically generates ULID primary keys and handles Snowflake timestamp formats.
Query Builder
$users = DB::connection('snowflake')->table('users')->get();
DB::connection('snowflake')->table('users')->insert([
'id' => Str::ulid()->toLower(),
'name' => 'John Doe',
'email' => 'john@example.com',
]);
DB::connection('snowflake')
->table('events')
->where('payload->type', 'purchase')
->get();
Migrations
use Illuminate\Database\Migrations\Migration;
use FoundryCo\Snowflake\Schema\SnowflakeBlueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
protected $connection = 'snowflake';
public function up(): void
{
Schema::connection('snowflake')->create('users', function (SnowflakeBlueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->variant('preferences');
$table->timestamps();
$table->clusterBy(['created_at', 'id']);
});
}
public function down(): void
{
Schema::connection('snowflake')->dropIfExists('users');
}
};
Snowflake Column Types
| Method | Snowflake Type |
|---|---|
id() |
CHAR(26) |
variant() |
VARIANT |
object() |
OBJECT |
array() |
ARRAY |
geography() |
GEOGRAPHY |
geometry() |
GEOMETRY |
timestampNtz() |
TIMESTAMP_NTZ |
timestampLtz() |
TIMESTAMP_LTZ |
timestampTz() |
TIMESTAMP_TZ |
number() |
NUMBER(p,s) |
identity() |
INTEGER IDENTITY |
Custom Casts
use FoundryCo\Snowflake\Casts\VariantCast;
use FoundryCo\Snowflake\Casts\SnowflakeTimestamp;
class Event extends Model
{
use UsesSnowflake;
protected $connection = 'snowflake';
protected $casts = [
'payload' => VariantCast::class,
'occurred_at' => SnowflakeTimestamp::class,
];
}
Warehouse & Role Switching
$connection = DB::connection('snowflake');
$connection->useWarehouse('ANALYTICS_WH');
$connection->useRole('ANALYST');
$connection->useSchema('STAGING');
Transactions
DB::connection('snowflake')->transaction(function ($db) {
$db->table('accounts')->where('id', 1)->decrement('balance', 100);
$db->table('accounts')->where('id', 2)->increment('balance', 100);
});
Cursors
foreach (DB::connection('snowflake')->table('events')->cursor() as $event) {
// Process one row at a time
}
Testing
composer test
Limitations
- No savepoints (Snowflake limitation)
- No row locking (Snowflake is append-only)
- No traditional indexes (use clustering keys instead)
License
MIT License. See LICENSE for details.