Skip to content

Commit b706763

Browse files
committed
wip
1 parent 8eb2a0b commit b706763

File tree

11 files changed

+718
-44
lines changed

11 files changed

+718
-44
lines changed

.github/workflows/run-tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
php: [8.4, 8.3, 8.2]
1515
laravel: ["10.*", "11.*", "12.*"]
1616
dependency-version: [prefer-lowest, prefer-stable]
17+
db_connection: [mysql, sqlite]
1718
include:
1819
- laravel: 10.*
1920
testbench: ^8.32
@@ -22,7 +23,7 @@ jobs:
2223
- laravel: 12.*
2324
testbench: ^10.0
2425

25-
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
26+
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.db_connection }} - ${{ matrix.dependency-version }}
2627

2728
services:
2829
mysql:
@@ -62,6 +63,7 @@ jobs:
6263
- name: Execute tests
6364
run: vendor/bin/phpunit
6465
env:
66+
DB_CONNECTION: ${{ matrix.db_connection }}
6567
DB_DATABASE: protone_media_db_test
6668
DB_USERNAME: protone_media_db_test
6769
DB_PASSWORD: secret

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@ This Laravel package allows you to search through multiple Eloquent models. It s
1616
## Requirements
1717

1818
* PHP 8.2 or higher
19-
* MySQL 8.0+
19+
* MySQL 8.0+ or SQLite 3.8+
2020
* Laravel 10.0+
2121

22+
## Database Support
23+
24+
This package supports both **MySQL** and **SQLite** databases. Most features work identically across both database systems, with the following limitations for SQLite:
25+
26+
* **Sounds Like**: The `soundsLike()` method falls back to regular `LIKE` matching since SQLite doesn't support the `SOUNDS LIKE` operator
27+
* **Full-Text Search**: MySQL's `MATCH() AGAINST()` full-text search falls back to `LIKE` matching on SQLite
28+
29+
All other features including cross-model search, pagination, sorting, and relationship searching work identically on both database systems.
30+
2231
## Features
2332

2433
* Search through one or more [Eloquent models](https://laravel.com/docs/master/eloquent).
@@ -253,6 +262,8 @@ Search::new()
253262
->search('framework -css');
254263
```
255264

265+
**Note**: On SQLite databases, full-text search automatically falls back to regular `LIKE` matching since SQLite doesn't support MySQL's `MATCH() AGAINST()` syntax.
266+
256267
### Sounds like
257268

258269
MySQL has a *soundex* algorithm built-in so you can search for terms that sound almost the same. You can use this feature by calling the `soundsLike` method:
@@ -265,6 +276,8 @@ Search::new()
265276
->search('larafel');
266277
```
267278

279+
**Note**: On SQLite databases, this feature automatically falls back to regular `LIKE` matching since SQLite doesn't support the `SOUNDS LIKE` operator.
280+
268281
### Eager load relationships
269282

270283
Not much to explain here, but this is supported as well :)

src/DatabaseGrammarFactory.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ProtoneMedia\LaravelCrossEloquentSearch;
4+
5+
use Illuminate\Database\Connection;
6+
use ProtoneMedia\LaravelCrossEloquentSearch\Grammars\MySqlSearchGrammar;
7+
use ProtoneMedia\LaravelCrossEloquentSearch\Grammars\SQLiteSearchGrammar;
8+
use ProtoneMedia\LaravelCrossEloquentSearch\Grammars\SearchGrammarInterface;
9+
10+
/**
11+
* Factory for creating database-specific search grammar instances.
12+
*
13+
* This factory provides database-agnostic access to search functionality by
14+
* creating the appropriate grammar implementation based on the database driver.
15+
* Currently supports MySQL/MariaDB and SQLite.
16+
*/
17+
class DatabaseGrammarFactory
18+
{
19+
/**
20+
* Create a search grammar instance for the given database connection.
21+
*
22+
* @param \Illuminate\Database\Connection $connection
23+
* @return \ProtoneMedia\LaravelCrossEloquentSearch\Grammars\SearchGrammarInterface
24+
* @throws \InvalidArgumentException
25+
*/
26+
public static function make(Connection $connection): SearchGrammarInterface
27+
{
28+
$driver = $connection->getDriverName();
29+
30+
switch ($driver) {
31+
case 'mysql':
32+
case 'mariadb':
33+
return new MySqlSearchGrammar($connection);
34+
case 'sqlite':
35+
return new SQLiteSearchGrammar($connection);
36+
default:
37+
throw new \InvalidArgumentException("Database driver '{$driver}' is not supported for cross-eloquent search.");
38+
}
39+
}
40+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ProtoneMedia\LaravelCrossEloquentSearch\Grammars;
4+
5+
use Illuminate\Database\Connection;
6+
use Illuminate\Database\Query\Grammars\MySqlGrammar;
7+
8+
/**
9+
* MySQL-specific search grammar implementation.
10+
*/
11+
class MySqlSearchGrammar implements SearchGrammarInterface
12+
{
13+
protected Connection $connection;
14+
protected MySqlGrammar $grammar;
15+
16+
/**
17+
* Create a new MySQL search grammar instance.
18+
*
19+
* @param \Illuminate\Database\Connection $connection
20+
*/
21+
public function __construct(Connection $connection)
22+
{
23+
$this->connection = $connection;
24+
$this->grammar = new MySqlGrammar($connection);
25+
}
26+
27+
/**
28+
* Wrap a column or table name with appropriate identifier quotes.
29+
*
30+
* @param mixed $value
31+
* @return string
32+
*/
33+
public function wrap($value): string
34+
{
35+
return $this->grammar->wrap($value);
36+
}
37+
38+
/**
39+
* Create a case-insensitive column expression.
40+
*
41+
* @param string $column
42+
* @return string
43+
*/
44+
public function caseInsensitive(string $column): string
45+
{
46+
return "LOWER({$column})";
47+
}
48+
49+
/**
50+
* Create a COALESCE expression with the given values.
51+
*
52+
* @param array $values
53+
* @return string
54+
*/
55+
public function coalesce(array $values): string
56+
{
57+
$valueList = implode(',', $values);
58+
return "COALESCE({$valueList})";
59+
}
60+
61+
/**
62+
* Create a character length expression for the given column.
63+
*
64+
* @param string $column
65+
* @return string
66+
*/
67+
public function charLength(string $column): string
68+
{
69+
return "CHAR_LENGTH({$column})";
70+
}
71+
72+
/**
73+
* Create a string replace expression.
74+
*
75+
* @param string $column
76+
* @param string $search
77+
* @param string $replace
78+
* @return string
79+
*/
80+
public function replace(string $column, string $search, string $replace): string
81+
{
82+
return "REPLACE({$column}, {$search}, {$replace})";
83+
}
84+
85+
/**
86+
* Create a lowercase expression for the given column.
87+
*
88+
* @param string $column
89+
* @return string
90+
*/
91+
public function lower(string $column): string
92+
{
93+
return "LOWER({$column})";
94+
}
95+
96+
/**
97+
* Get the operator used for phonetic/sounds-like matching.
98+
*
99+
* @return string
100+
*/
101+
public function soundsLikeOperator(): string
102+
{
103+
return 'sounds like';
104+
}
105+
106+
/**
107+
* Check if the database supports phonetic/sounds-like matching.
108+
*
109+
* @return bool
110+
*/
111+
public function supportsSoundsLike(): bool
112+
{
113+
return true;
114+
}
115+
116+
/**
117+
* Check if the database supports complex ordering in UNION queries.
118+
*
119+
* @return bool
120+
*/
121+
public function supportsUnionOrdering(): bool
122+
{
123+
return true;
124+
}
125+
126+
/**
127+
* Wrap a UNION query for databases that need special handling.
128+
*
129+
* @param string $sql
130+
* @param array $bindings
131+
* @return array
132+
*/
133+
public function wrapUnionQuery(string $sql, array $bindings): array
134+
{
135+
return ['sql' => $sql, 'bindings' => $bindings];
136+
}
137+
}

0 commit comments

Comments
 (0)