From c2e25352296254780a2dfa968b8ca0d6ec009306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Biarda?= <1135380+michalbiarda@users.noreply.github.com> Date: Wed, 13 May 2026 18:07:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(database-pgsql):=20normalise=20jsonb=20?= =?UTF-8?q?=E2=86=92=20json=20in=20introspector=20type=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PgSqlGenerator maps entity type 'json' to JSONB in DDL, which is correct (JSONB is preferred in PostgreSQL for indexing and performance). However, the introspector returned 'jsonb' for those columns, while the entity schema still held 'json'. With no alias in Column::typeEquals(), the diff calculator reported a spurious Modify on every json column after the initial migration. Mapping 'jsonb' → 'json' in PgSqlIntrospector::TYPE_MAP normalises the round-trip so introspected JSONB columns compare equal to entity-declared json columns. The fix is intentionally scoped to the PgSQL driver — MySQL is unaffected (it stores and introspects JSON under the same name). Co-Authored-By: Claude Sonnet 4.6 --- .../src/Introspection/PgSqlIntrospector.php | 2 +- .../Introspection/PgSqlIntrospectorTest.php | 47 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/database-pgsql/src/Introspection/PgSqlIntrospector.php b/packages/database-pgsql/src/Introspection/PgSqlIntrospector.php index 450a7124..493125c6 100644 --- a/packages/database-pgsql/src/Introspection/PgSqlIntrospector.php +++ b/packages/database-pgsql/src/Introspection/PgSqlIntrospector.php @@ -51,7 +51,7 @@ 'double precision' => 'double', 'float8' => 'double', 'json' => 'json', - 'jsonb' => 'jsonb', + 'jsonb' => 'json', 'uuid' => 'uuid', 'bytea' => 'blob', ]; diff --git a/packages/database-pgsql/tests/Introspection/PgSqlIntrospectorTest.php b/packages/database-pgsql/tests/Introspection/PgSqlIntrospectorTest.php index 1ca265aa..12c55c3a 100644 --- a/packages/database-pgsql/tests/Introspection/PgSqlIntrospectorTest.php +++ b/packages/database-pgsql/tests/Introspection/PgSqlIntrospectorTest.php @@ -6,6 +6,7 @@ use Marko\Database\Connection\ConnectionInterface; use Marko\Database\Connection\StatementInterface; +use Marko\Database\Diff\DiffCalculator; use Marko\Database\Introspection\IntrospectorInterface; use Marko\Database\PgSql\Introspection\PgSqlIntrospector; use Marko\Database\Schema\Column; @@ -152,13 +153,57 @@ function (string $sql, array $bindings) use (&$queriedSql, &$queriedBindings): a ->and($columns[11]->type)->toBe('date') ->and($columns[12]->type)->toBe('time') ->and($columns[13]->type)->toBe('json') - ->and($columns[14]->type)->toBe('jsonb') + ->and($columns[14]->type)->toBe('json') ->and($columns[15]->type)->toBe('uuid') ->and($columns[16]->type)->toBe('char') ->and($columns[16]->length)->toBe(10) ->and($columns[17]->type)->toBe('blob'); }); + it('normalises jsonb to json so introspected columns match entity-declared type', function (): void { + $connection = createTestConnection(function (string $sql): array { + if (str_contains($sql, 'information_schema.columns')) { + return [ + ['column_name' => 'data', 'data_type' => 'jsonb', 'character_maximum_length' => null, 'is_nullable' => 'NO', 'column_default' => null, 'is_identity' => 'NO', 'identity_generation' => null], + ]; + } + + return []; + }); + + $introspector = new PgSqlIntrospector($connection); + $columns = $introspector->getColumns('products'); + + expect($columns[0]->type)->toBe('json'); + }); + + it('produces no diff when entity declares json and database stores jsonb', function (): void { + $entitySchema = [ + 'products' => new Table( + name: 'products', + columns: [new Column(name: 'data', type: 'json')], + indexes: [], + ), + ]; + + $connection = createTestConnection(function (string $sql): array { + if (str_contains($sql, 'information_schema.columns')) { + return [ + ['column_name' => 'data', 'data_type' => 'jsonb', 'character_maximum_length' => null, 'is_nullable' => 'NO', 'column_default' => null, 'is_identity' => 'NO', 'identity_generation' => null], + ]; + } + + return []; + }); + + $introspector = new PgSqlIntrospector($connection); + $databaseSchema = ['products' => $introspector->getTable('products')]; + + $diff = (new DiffCalculator())->calculate($entitySchema, $databaseSchema); + + expect($diff->isEmpty())->toBeTrue(); + }); + it('detects nullable columns', function (): void { $connection = createTestConnection(function (string $sql): array { if (str_contains($sql, 'information_schema.columns')) {