jgamboa / laravel-aurora-dsql
Laravel driver for Amazon Aurora DSQL: IAM token auth, DSQL-compatible schema builder (inline PK/FK, ASYNC indexes, identity) and migration helpers.
Requires
- php: ^8.2
- awslabs/aurora-dsql-pdo-pgsql: ^0.1.1
- illuminate/database: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-22 00:30:31 UTC
README
Laravel (12+) database driver for Amazon Aurora DSQL.
- Connects with an IAM auth token through the official
awslabs/aurora-dsql-pdo-pgsqlconnector (SSLverify-full, OCC retries). - DSQL-compatible schema builder: regular Laravel migrations just work.
primary()andforeign()/constrained()are emitted inline inCREATE TABLE.index()/unique()→CREATE [UNIQUE] INDEX ASYNC, waiting for the index build to finish.id()/increments()→bigint generated by default as identity (cache 65536).- No schema transactions (DSQL allows a single DDL statement per transaction).
Schema::getTables(),db:show,db:wipeandmigrate:freshwork.
DsqlMigrationbase class for raw SQL migrations.
Nested transactions
Aurora DSQL does not support SAVEPOINT, so the connection reports supportsSavepoints() === false and a
nested DB::transaction() runs inside the outer transaction. An exception caught inside the nested
closure does not undo that closure's writes; one that reaches the outermost transaction rolls back
everything (and an OCC conflict retries the outermost closure).
Installation
composer require jgamboa/laravel-aurora-dsql
The service provider is auto-discovered. Add a connection to config/database.php:
'dsql' => [ 'driver' => 'dsql', 'host' => env('DSQL_HOST'), // xxxx.dsql.us-east-1.on.aws 'region' => env('AWS_REGION', 'us-east-1'), 'username' => env('DSQL_USER', 'admin'), // 'admin' => dsql:DbConnectAdmin; other roles => dsql:DbConnect 'database' => 'postgres', // DSQL has a single database per cluster 'profile' => env('DSQL_PROFILE'), // local AWS profile (e.g. SSO). Leave empty on Lambda/EC2/ECS 'sslrootcert' => env('DSQL_SSLROOTCERT', 'system'), // 'system' requires libpq 17+ 'occ_max_retries' => 3, 'wait_for_indexes' => true, // wait for CREATE INDEX ASYNC jobs 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'public', ],
Usage
// A regular migration (no raw SQL) return new class extends Migration { protected $connection = 'dsql'; public function up(): void { Schema::create('products', function (Blueprint $table) { $table->uuid('id')->primary(); $table->foreignUuid('category_id')->constrained()->cascadeOnDelete(); $table->string('sku')->unique(); $table->timestamps(); }); } };
// Model: UUID v7 primary keys with HasUuids (Laravel 12+) class Product extends Model { use HasUuids; protected $connection = 'dsql'; }
Optimistic concurrency
DSQL uses optimistic concurrency control: a conflicting write fails with SQLSTATE 40001 (OC000/OC001), frequently at COMMIT. This package retries those conflicts for you, with exponential backoff (base 50 ms, cap 1000 ms, jitter):
DB::connection('dsql')->transaction(fn () => ...)re-runs the whole closure, up toocc_max_retries + 1attempts (default 4). Passingattempts:explicitly overrides this. The closure must be idempotent: it can run several times, so avoid side effects such as sending emails or dispatching non-transactional jobs inside it.- Autocommit writes (
INSERT/UPDATE/DELETEoutside a transaction, e.g. Eloquentsave()) are retried the same way. - Writes inside an open transaction are not retried individually: the conflict bubbles up to the outermost
transaction()call, which retries the whole closure. Nested transactions do not retry on their own. - Other errors are never retried, and the original exception is rethrown once attempts are exhausted.
Configure it per connection with 'occ_max_retries' => 3.
DSQL limitations (verified against a live cluster)
| Not supported | Alternative |
|---|---|
| Adding a PK / FK / UNIQUE / CHECK to an existing table | Declare them in Schema::create(); for unique use ->unique() (an index) |
ADD COLUMN with DEFAULT, NOT NULL or a FK |
Add it nullable → SET DEFAULT → backfill |
->change() (changing a column type), SET NOT NULL |
New table + copy + rename |
| Nested transactions (SAVEPOINT) | Avoid DB::transaction() inside another one |
TRUNCATE |
delete() |
Temporary tables, triggers, plpgsql, extensions, advisory locks, sharedLock() |
— |
| Full-text search with a language config | Only simple is available |
CREATE DATABASE |
One cluster per database |
Supported: lockForUpdate, upsert (needs a unique index), JSON/JSONB, CTEs, window functions, views, sequences, language sql functions.
Operational notes: connections last at most 60 minutes (long-running processes must reconnect); the IAM token is signed locally (no network call).
Testing
vendor/bin/phpunit --testsuite Unit DSQL_HOST=xxxx.dsql.us-east-1.on.aws DSQL_PROFILE=my-profile vendor/bin/phpunit --testsuite Integration
License
MIT