fix(brain): Postgres portability for brain connection + migrations

Three related fixes so the brain DB works on Postgres, not just MariaDB:

1. config.php — brain charset/collation was hardcoded to utf8mb4 which
   Postgres rejects as client_encoding. Now driver-aware: utf8 for
   pgsql, utf8mb4 otherwise. Override via BRAIN_DB_CHARSET env var.

2. Migration 000008 (create_brain_memories) — self-referential FK on
   supersedes_id was declared inside Schema::create{}, causing Postgres
   to evaluate it before the PK index existed ('no unique constraint
   matching given keys'). Split into Schema::create + separate
   Schema::table to guarantee PK is in place when FK is added.

3. Migration 000009 (drop workspace FK) — try/catch inside the Blueprint
   closure couldn't catch deferred SQL failures. Replaced with a
   constraint-exists pre-query against information_schema, supporting
   both pgsql and mariadb/mysql drivers. Fresh installs no longer fail
   trying to drop a constraint that was never created.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-04-22 21:34:48 +01:00
parent e58986a3b4
commit 4e190dc7ec
3 changed files with 40 additions and 8 deletions

View file

@ -43,7 +43,15 @@ return new class extends Migration
$table->index('agent_id');
$table->index(['workspace_id', 'type']);
$table->index(['workspace_id', 'project']);
});
// Self-referential FK added AFTER create so Postgres sees the
// primary key on `id` when evaluating the constraint. Adding it
// inside the create{} block ordered the FK before the PK index
// on some Postgres versions, breaking with:
// SQLSTATE[42830]: there is no unique constraint matching
// given keys for referenced table "brain_memories"
$schema->table('brain_memories', function (Blueprint $table) {
$table->foreign('supersedes_id')
->references('id')
->on('brain_memories')

View file

@ -25,13 +25,34 @@ return new class extends Migration
return;
}
$schema->table('brain_memories', function (Blueprint $table) {
try {
// Laravel Blueprint defers statement execution until the closure
// returns, so a try/catch INSIDE the closure doesn't catch the
// deferred SQL's failure. Instead, check the constraint exists
// before issuing the drop, across both pgsql + mariadb backends.
$conn = $schema->getConnection();
$driver = $conn->getDriverName();
$constraint = 'brain_memories_workspace_id_foreign';
$exists = match ($driver) {
'pgsql' => (bool) $conn->selectOne(
"SELECT 1 FROM information_schema.table_constraints
WHERE table_name = 'brain_memories' AND constraint_name = ?",
[$constraint],
),
'mariadb', 'mysql' => (bool) $conn->selectOne(
"SELECT 1 FROM information_schema.table_constraints
WHERE table_schema = DATABASE() AND table_name = 'brain_memories'
AND constraint_name = ?",
[$constraint],
),
default => false, // unknown driver — don't attempt the drop
};
if ($exists) {
$schema->table('brain_memories', function (Blueprint $table) {
$table->dropForeign(['workspace_id']);
} catch (\Throwable) {
// FK doesn't exist — fresh install, nothing to drop.
}
});
});
}
}
public function down(): void

View file

@ -95,8 +95,11 @@ return [
'database' => env('BRAIN_DB_DATABASE', env('DB_DATABASE', 'forge')),
'username' => env('BRAIN_DB_USERNAME', env('DB_USERNAME', 'forge')),
'password' => env('BRAIN_DB_PASSWORD', env('DB_PASSWORD', '')),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
// charset defaults: utf8 for pgsql (Postgres rejects 'utf8mb4'),
// utf8mb4 for everything else (MariaDB/MySQL). Override with
// BRAIN_DB_CHARSET if your instance needs something specific.
'charset' => env('BRAIN_DB_CHARSET', env('BRAIN_DB_DRIVER', env('DB_CONNECTION', 'mariadb')) === 'pgsql' ? 'utf8' : 'utf8mb4'),
'collation' => env('BRAIN_DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
],
],