diff --git a/packages/support/src/helpers.php b/packages/support/src/helpers.php index 5569b5598e..d218ae3b56 100644 --- a/packages/support/src/helpers.php +++ b/packages/support/src/helpers.php @@ -227,7 +227,7 @@ function generate_loading_indicator_html(?ComponentAttributeBag $attributes = nu /** * @internal This function is only to be used internally by Filament and is subject to change at any time. Please do not use this function in your own code. */ - function generate_search_column_expression(string $column, ?bool $isSearchForcedCaseInsensitive, Connection $databaseConnection): string | Expression + function generate_search_column_expression(string $column, ?bool $isSearchForcedCaseInsensitive, Connection $databaseConnection): Expression { $driverName = $databaseConnection->getDriverName(); @@ -272,11 +272,11 @@ function generate_search_column_expression(string $column, ?bool $isSearchForced $isSearchForcedCaseInsensitive ??= match ($driverName) { 'pgsql' => true, - default => str($column)->contains('json_extract('), + default => str($column)->contains('json_extract(') || str($column)->contains('->'), }; if ($isSearchForcedCaseInsensitive) { - if (in_array($driverName, ['mysql', 'mariadb'], true) && str($column)->contains('->') && ! str($column)->startsWith('json_extract(')) { + if (in_array($driverName, ['mysql', 'mariadb', 'sqlite'], true) && str($column)->contains('->') && ! str($column)->startsWith('json_extract(')) { [$field, $path] = invade($databaseConnection->getQueryGrammar())->wrapJsonFieldAndPath($column); /** @phpstan-ignore-line */ $column = "json_extract({$field}{$path})"; } @@ -290,14 +290,7 @@ function generate_search_column_expression(string $column, ?bool $isSearchForced $column = "{$column} collate {$collation}"; } - if ( - str($column)->contains('(') || // This checks if the column name probably contains a raw expression like `lower()` or `json_extract()`. - filled($collation) - ) { - return new Expression($column); - } - - return $column; + return new Expression($column); } } @@ -307,12 +300,7 @@ function generate_search_column_expression(string $column, ?bool $isSearchForced */ function generate_search_term_expression(string $search, ?bool $isSearchForcedCaseInsensitive, Connection $databaseConnection): string { - $isSearchForcedCaseInsensitive ??= match ($databaseConnection->getDriverName()) { - 'pgsql' => true, - default => false, - }; - - if (! $isSearchForcedCaseInsensitive) { + if ($isSearchForcedCaseInsensitive === false) { return $search; } diff --git a/packages/tables/src/Table/Concerns/CanSearchRecords.php b/packages/tables/src/Table/Concerns/CanSearchRecords.php index 0740243505..7352096f0f 100644 --- a/packages/tables/src/Table/Concerns/CanSearchRecords.php +++ b/packages/tables/src/Table/Concerns/CanSearchRecords.php @@ -35,6 +35,8 @@ trait CanSearchRecords protected ?Closure $searchUsing = null; + protected bool | Closure | null $isSearchForcedCaseInsensitive = null; + public function persistSearchInSession(bool | Closure $condition = true): static { $this->persistsSearchInSession = $condition; @@ -205,29 +207,31 @@ public function applyExtraSearchConstraints(Builder $query, string $search, bool $model = $query->getModel(); - $nonTranslatableSearch = generate_search_term_expression($search, isSearchForcedCaseInsensitive: false, databaseConnection: $databaseConnection); + $isSearchForcedCaseInsensitive = $this->isSearchForcedCaseInsensitive(); + + $nonTranslatableSearch = generate_search_term_expression($search, isSearchForcedCaseInsensitive: $isSearchForcedCaseInsensitive, databaseConnection: $databaseConnection); $translatableContentDriver = $this->getLivewire()->makeFilamentTranslatableContentDriver(); $query->when( $translatableContentDriver?->isAttributeTranslatable($model::class, attribute: $column), - fn (Builder $query): Builder => $translatableContentDriver->applySearchConstraintToQuery($query, $column, $search, $whereClause, isSearchForcedCaseInsensitive: false), + fn (Builder $query): Builder => $translatableContentDriver->applySearchConstraintToQuery($query, $column, $search, $whereClause, isSearchForcedCaseInsensitive: $isSearchForcedCaseInsensitive), fn (Builder $query) => $query->when( $this->getExtraSearchableColumnRelationship($column, $query->getModel()), fn (Builder $query): Builder => $query->{"{$whereClause}Relation"}( (string) str($column)->beforeLast('.'), - generate_search_column_expression((string) str($column)->afterLast('.'), isSearchForcedCaseInsensitive: false, databaseConnection: $databaseConnection), + generate_search_column_expression((string) str($column)->afterLast('.'), isSearchForcedCaseInsensitive: $isSearchForcedCaseInsensitive, databaseConnection: $databaseConnection), 'like', "%{$nonTranslatableSearch}%", ), - function (Builder $query) use ($databaseConnection, $nonTranslatableSearch, $column, $whereClause): Builder { + function (Builder $query) use ($databaseConnection, $nonTranslatableSearch, $column, $whereClause, $isSearchForcedCaseInsensitive): Builder { // Treat the missing "relationship" as a JSON column if dot notation is used in the column name. if (str($column)->contains('.')) { $column = (string) str($column)->replace('.', '->'); } return $query->{$whereClause}( - generate_search_column_expression($column, isSearchForcedCaseInsensitive: false, databaseConnection: $databaseConnection), + generate_search_column_expression($column, isSearchForcedCaseInsensitive: $isSearchForcedCaseInsensitive, databaseConnection: $databaseConnection), 'like', "%{$nonTranslatableSearch}%", ); @@ -298,4 +302,16 @@ public function callSearchUsing(Builder $query, string $search): void 'search' => $search, ]); } + + public function forceSearchCaseInsensitive(bool | Closure | null $condition = true): static + { + $this->isSearchForcedCaseInsensitive = $condition; + + return $this; + } + + public function isSearchForcedCaseInsensitive(): ?bool + { + return $this->evaluate($this->isSearchForcedCaseInsensitive); + } }