diff --git a/composer.json b/composer.json index b9266041..fd1cfe4e 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,7 @@ "php": "^7.4.1 | ^8.0 | ^8.1", "livewire/livewire": "^2.4", "box/spout": "^3", - "doctrine/dbal": "^3.1", - "friendsofphp/php-cs-fixer": "^3.2" + "doctrine/dbal": "^3.1" }, "scripts": { "test": "@test:sqlite", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9c52bfc7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +version: '3' +services: + mysql: + container_name: 'mysql_test' + image: 'mysql/mysql-server:8.0' + ports: + - '3307:3306' + command: '--default-authentication-plugin=mysql_native_password' + environment: + MYSQL_ROOT_PASSWORD: 'password' + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: 'powergridtest' + MYSQL_USER: 'powergrid' + MYSQL_PASSWORD: 'password' + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + networks: + - sail + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-ppassword" ] + retries: 3 + timeout: 5s + pgsql: + container_name: 'pgsql_test' + image: 'postgres:13' + ports: + - '5433:5432' + environment: + PGPASSWORD: 'password' + POSTGRES_DB: 'powergridtest' + POSTGRES_USER: 'postgres' + POSTGRES_PASSWORD: 'password' + networks: + - sail + healthcheck: + test: [ "CMD", "pg_isready", "-q", "-d", "powergridtest", "-U", "postgres" ] + retries: 3 + timeout: 5s + sqlsrv: + container_name: 'sqlsrv_test' + image: 'mcr.microsoft.com/mssql/server:2017-latest' + ports: + - '1434:1433' + environment: + ACCEPT_EULA: 'Y' + SA_PASSWORD: 'yourStrong(!)Password' + networks: + - sail + healthcheck: + test: [ "CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "yourStrong(!)Password", "-Q", "select 1" ] + retries: 3 + timeout: 5s +networks: + sail: + driver: bridge diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 05dfcb2d..d2482e6b 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -2,10 +2,18 @@ namespace PowerComponents\LivewirePowerGrid\Helpers; -use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\{DB, Schema}; class SqlSupport { + /** + * @var array|string[] + */ + private static array $sortStringNumberTypes = ['string', 'varchar', 'char']; + + /** + * @return string + */ public static function like(): string { $driverName = DB::getDriverName(); @@ -16,4 +24,64 @@ public static function like(): string return $likeSyntax[$driverName] ?? 'LIKE'; } + + /** + * @param string $sortField + * @return string + */ + public static function sortStringAsNumber(string $sortField): string + { + $driverName = DB::getDriverName(); + + return self::getSortSqlByDriver($sortField, $driverName); + } + + /** + * @param string $sortField + * @param string $driverName + * @param string $driverVersion + * @return string + */ + private static function getSortSqlByDriver(string $sortField, string $driverName, string $driverVersion = '*'): string + { + $sqlByDriver = [ + 'sqlite' => [ + '*' => "CAST($sortField AS INTEGER)", + ], + 'mysql' => [ + '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", + ], + 'pgsql' => [ + '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER)", + ], + 'sqlsrv' => [ + '*' => "CAST(SUBSTRING($sortField, PATINDEX('%[a-z]%', $sortField), LEN($sortField)-PATINDEX('%[a-z]%', $sortField)) AS INT)", + ], + ]; + + return $sqlByDriver[$driverName][$driverVersion] ?? $sortField; + } + + /** + * @param string $sortFieldType + * @return bool + */ + public static function isValidSortFieldType(string $sortFieldType): bool + { + return in_array($sortFieldType, self::$sortStringNumberTypes); + } + + /** + * @param string $sortField + * @return string + */ + public static function getSortFieldType(string $sortField): string + { + $data = explode('.', $sortField); + + return Schema::getConnection() + ->getDoctrineColumn($data[0], $data[1]) + ->getType() + ->getName(); + } } diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index da4409b3..166b8cc2 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -9,7 +9,7 @@ use Illuminate\Pagination\{AbstractPaginator, LengthAwarePaginator}; use Illuminate\Support\{Collection as BaseCollection, Str}; use Livewire\{Component, WithPagination}; -use PowerComponents\LivewirePowerGrid\Helpers\{Collection, Model}; +use PowerComponents\LivewirePowerGrid\Helpers\{Collection, Model, SqlSupport}; use PowerComponents\LivewirePowerGrid\Themes\ThemeBase; use PowerComponents\LivewirePowerGrid\Traits\{BatchableExport, Checkbox, Exportable, Filter, WithSorting}; @@ -174,7 +174,7 @@ public function mount(): void */ public function showPerPage(int $perPage = 10): PowerGridComponent { - if (\Str::contains((string) $perPage, $this->perPageValues)) { + if (Str::contains((string) $perPage, $this->perPageValues)) { $this->perPageInput = true; $this->perPage = $perPage; } @@ -367,6 +367,8 @@ public function fillData() $sortField = $this->currentTable . '.' . $this->sortField; } + $sortFieldType = SqlSupport::getSortFieldType($sortField); + /** @var Builder $results */ $results = $this->resolveModel($datasource) ->where(function (Builder $query) { @@ -379,8 +381,8 @@ public function fillData() ->filter(); }); - if ($this->withSortStringNumber) { - $results->orderByRaw("$sortField+0 $this->sortDirection"); + if ($this->withSortStringNumber && SqlSupport::isValidSortFieldType($sortFieldType)) { + $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); } $results = $results->orderBy($sortField, $this->sortDirection); @@ -419,6 +421,10 @@ private function transform($results) }); } + /** + * @param string $field + * @throws Exception + */ public function toggleColumn(string $field): void { $this->columns = collect($this->columns)->map(function ($column) use ($field) { diff --git a/tests/Models/Category.php b/tests/Models/Category.php index 8c75a3b4..b6774ca9 100644 --- a/tests/Models/Category.php +++ b/tests/Models/Category.php @@ -7,7 +7,7 @@ /** * @property int $id - * @property int $name + * @property string $name * @property Carbon $created_at * @property Carbon $updated_at */