Merge pull request #17238 from maytham553/fix/postgresql-json-search-syntax-error

Fix PostgreSQL JSON field search syntax error (fixes #17237)
This commit is contained in:
Dan Harrin
2025-08-10 20:27:42 +01:00
committed by GitHub
2 changed files with 79 additions and 19 deletions
+31 -19
View File
@@ -166,26 +166,38 @@ if (! function_exists('Filament\Support\generate_search_column_expression')) {
$column = match ($driverName) {
'pgsql' => (
str($column)->contains('->')
? (
// Handle `table.field` part with double quotes
str($column)
->before('->')
->explode('.')
->map(fn (string $part): string => (string) str($part)->wrap('"'))
->implode('.')
) . collect(str($column)->after('->')->explode('->')) // Handle JSON path parts
->map(function ($segment, $index) use ($column): string {
$totalParts = substr_count($column, '->');
? (
// Handle `table.field` part with double quotes
str($column)
->before('->')
->explode('.')
->map(fn (string $part): string => (string) str($part)->wrap('"'))
->implode('.')
) . collect(str($column)->after('->')->explode('->')) // Handle JSON path parts
->map(function ($segment, $index) use ($column): string {
// If segment already starts with `>` (from `->>` operator), preserve it
$isExplicitOperatorPrefixed = str($segment)->startsWith('>');
$segment = $isExplicitOperatorPrefixed ? (string) str($segment)->after('>') : $segment;
return ($index === ($totalParts - 1))
? "->>'{$segment}'"
: "->'{$segment}'";
})
->implode('')
: str($column)
->explode('.')
->map(fn (string $part): string => (string) str($part)->wrap('"'))
->implode('.')
// Remove single quotes from segment if present to avoid redundant quoting
$isWrappedWithSingleQuotes = str($segment)->startsWith("'") && str($segment)->endsWith("'");
$segment = $isWrappedWithSingleQuotes ? (string) str($segment)->trim("'") : $segment;
if ($isExplicitOperatorPrefixed) {
return "->>'{$segment}'";
}
$totalParts = substr_count($column, '->');
return ($index === ($totalParts - 1))
? "->>'{$segment}'"
: "->'{$segment}'";
})
->implode('')
: str($column)
->explode('.')
->map(fn (string $part): string => (string) str($part)->wrap('"'))
->implode('.')
) . '::text',
default => $column,
};
+48
View File
@@ -112,3 +112,51 @@ it('will generate a column expression for Postgres with colons in the table name
['blog:posts.title', 'lower("blog:posts"."title"::text)'],
['blog:posts:comments.author.name', 'lower("blog:posts:comments"."author"."name"::text)'],
]);
it('will generate a JSON search column expression for Postgres with explicit ->> operator', function () {
$column = 'data->>name';
$isSearchForcedCaseInsensitive = true;
$databaseConnection = Mockery::mock(Connection::class);
$databaseConnection->shouldReceive('getDriverName')->andReturn('pgsql');
$databaseConnection->shouldReceive('getConfig')->with('search_collation')->andReturn(null);
$grammar = new PostgresGrammar($databaseConnection);
$expression = generate_search_column_expression($column, $isSearchForcedCaseInsensitive, $databaseConnection);
expect($expression->getValue($grammar))
->toBe("lower(\"data\"->>'name'::text)");
});
it('will generate a nested JSON search column expression for Postgres with explicit ->> operator on the last segment', function () {
$column = 'data->name->>ar';
$isSearchForcedCaseInsensitive = true;
$databaseConnection = Mockery::mock(Connection::class);
$databaseConnection->shouldReceive('getDriverName')->andReturn('pgsql');
$databaseConnection->shouldReceive('getConfig')->with('search_collation')->andReturn(null);
$grammar = new PostgresGrammar($databaseConnection);
$expression = generate_search_column_expression($column, $isSearchForcedCaseInsensitive, $databaseConnection);
expect($expression->getValue($grammar))
->toBe("lower(\"data\"->'name'->>'ar'::text)");
});
it('will generate a JSON search column expression for Postgres with explicit ->> operator and simple key', function () {
$column = 'name->>\'en\'';
$isSearchForcedCaseInsensitive = true;
$databaseConnection = Mockery::mock(Connection::class);
$databaseConnection->shouldReceive('getDriverName')->andReturn('pgsql');
$databaseConnection->shouldReceive('getConfig')->with('search_collation')->andReturn(null);
$grammar = new PostgresGrammar($databaseConnection);
$expression = generate_search_column_expression($column, $isSearchForcedCaseInsensitive, $databaseConnection);
expect($expression->getValue($grammar))
->toBe("lower(\"name\"->>'en'::text)");
});