diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 47f3119..2771271 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -3,61 +3,70 @@ name: run-tests on: [push, pull_request] jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - php: [8.1, 8.0] - laravel: [9.*, 8.*] - dependency-version: [prefer-lowest, prefer-stable] - include: - - laravel: 9.* - testbench: 7.* - - laravel: 8.* - testbench: 6.* - - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: no - MYSQL_USER: protone_media_db_test - MYSQL_DATABASE: protone_media_db_test - MYSQL_PASSWORD: secret - MYSQL_ROOT_PASSWORD: secret - ports: - - 3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.composer/cache/files - key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, mysql, mysqli, pdo_mysql - coverage: none - - - name: Install dependencies - run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update - composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest - - - name: Execute tests - run: vendor/bin/phpunit - env: - DB_DATABASE: protone_media_db_test - DB_USERNAME: protone_media_db_test - DB_PASSWORD: secret - DB_PORT: ${{ job.services.mysql.ports[3306] }} + test: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.1, 8.0] + laravel: [9.*, 8.*] + db: [mysql, sqlite] + dependency-version: [prefer-lowest, prefer-stable] + include: + - laravel: 9.* + testbench: 7.* + - laravel: 8.* + testbench: 6.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - DB ${{ matrix.db }} - ${{ matrix.dependency-version }} + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: no + MYSQL_USER: protone_media_db_test + MYSQL_DATABASE: protone_media_db_test + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: secret + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, mysql, mysqli, pdo_mysql + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest + + - name: Execute tests (MySQL) + run: vendor/bin/phpunit + if: ${{ matrix.db == 'mysql' }} + env: + DB_DATABASE: protone_media_db_test + DB_USERNAME: protone_media_db_test + DB_PASSWORD: secret + DB_PORT: ${{ job.services.mysql.ports[3306] }} + + - name: Execute tests (SQLite) + run: vendor/bin/phpunit + if: ${{ matrix.db == 'sqlite' }} + env: + DB_CONNECTION: sqlite + DB_DATABASE: ":memory:" diff --git a/README.md b/README.md index 96d92e9..6b29480 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Hey! We've built a Docker-based deployment tool to launch apps and sites fully c ## Requirements * PHP 8.0 + 8.1 -* MySQL 5.7+ +* MySQL 5.7+ / SQLite3 * Laravel 8.0 or 9.0 ## Features diff --git a/src/ModelToSearchThrough.php b/src/ModelToSearchThrough.php index c929bc9..a616f28 100644 --- a/src/ModelToSearchThrough.php +++ b/src/ModelToSearchThrough.php @@ -138,7 +138,8 @@ public function getModel(): Model */ public function getModelKey($suffix = 'key'): string { - return implode('_', [ + // prefix with _ for SQLite support + return '_' . implode('_', [ $this->key, Str::snake(class_basename($this->getModel())), $suffix, diff --git a/src/Searcher.php b/src/Searcher.php index 652f9ce..738ac39 100644 --- a/src/Searcher.php +++ b/src/Searcher.php @@ -8,6 +8,7 @@ use Illuminate\Database\Query\Builder as BaseBuilder; use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Grammars\MySqlGrammar; +use Illuminate\Database\SQLiteConnection; use Illuminate\Pagination\Paginator; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -509,13 +510,19 @@ private function addRelevanceQueryToBuilder($builder, $modelToSearchThrough) throw OrderByRelevanceException::new(); } - $expressionsAndBindings = $modelToSearchThrough->getQualifiedColumns()->flatMap(function ($field) { + $lengthFunctionName = $this->usesSQLiteConnection() ? 'LENGTH' : 'CHAR_LENGTH'; + + $expressionsAndBindings = $modelToSearchThrough->getQualifiedColumns()->flatMap(function ($field) use ($lengthFunctionName) { $field = (new MySqlGrammar)->wrap($field); - return $this->termsWithoutWildcards->map(function ($term) use ($field) { + return $this->termsWithoutWildcards->map(function ($term) use ($field, $lengthFunctionName) { return [ - 'expression' => "COALESCE(CHAR_LENGTH(LOWER({$field})) - CHAR_LENGTH(REPLACE(LOWER({$field}), ?, ?)), 0)", - 'bindings' => [Str::lower($term), Str::substr(Str::lower($term), 1)], + 'expression' => sprintf( + 'COALESCE(%1$s(LOWER(%2$s)) - %1$s(REPLACE(LOWER(%2$s), ?, ?)), 0)', + $lengthFunctionName, + $field + ), + 'bindings' => [Str::lower($term), Str::substr(Str::lower($term), 1)], ]; }); }); @@ -574,7 +581,7 @@ protected function makeOrderBy(): string { $modelOrderKeys = $this->modelsToSearchThrough->map->getModelKey('order')->implode(','); - return "COALESCE({$modelOrderKeys})"; + return "COALESCE({$modelOrderKeys}, NULL)"; } /** @@ -587,7 +594,7 @@ protected function makeOrderByModel(): string { $modelOrderKeys = $this->modelsToSearchThrough->map->getModelKey('model_order')->implode(','); - return "COALESCE({$modelOrderKeys})"; + return "COALESCE({$modelOrderKeys}, NULL)"; } /** @@ -776,4 +783,14 @@ public function search(string $terms = null) ->pipe(fn (Collection $models) => new EloquentCollection($models)) ->when($this->pageName, fn (EloquentCollection $models) => $results->setCollection($models)); } + + /** + * Returns true if the current connection driver is SQLite, otherwise false. + * + * @return bool + */ + private function usesSQLiteConnection(): bool + { + return $this->modelsToSearchThrough->first()->getModel()->getConnection() instanceof SQLiteConnection; + } } diff --git a/tests/create_tables.php b/tests/create_tables.php index 37505f2..a03fc86 100644 --- a/tests/create_tables.php +++ b/tests/create_tables.php @@ -43,9 +43,11 @@ public function up() $table->string('subtitle'); $table->string('body'); - $table->fullText('title'); - $table->fullText(['title', 'subtitle']); - $table->fullText(['title', 'subtitle', 'body']); + if (config('database.default') === 'mysql') { + $table->fullText('title'); + $table->fullText(['title', 'subtitle']); + $table->fullText(['title', 'subtitle', 'body']); + } $table->unsignedInteger('video_id')->nullable(); @@ -58,9 +60,11 @@ public function up() $table->string('subtitle')->nullable(); $table->string('body')->nullable(); - $table->fullText('title'); - $table->fullText(['title', 'subtitle']); - $table->fullText(['title', 'subtitle', 'body']); + if (config('database.default') === 'mysql') { + $table->fullText('title'); + $table->fullText(['title', 'subtitle']); + $table->fullText(['title', 'subtitle', 'body']); + } $table->unsignedInteger('video_id')->nullable();