Skip to content

Commit b54ca12

Browse files
authored
Add support for whereFullText (#30)
SingleStore's syntax is slightly different from MySQL https://docs.singlestore.com/managed-service/en/reference/sql-reference/full-text-search-functions/match.html
1 parent e36e749 commit b54ca12

File tree

6 files changed

+200
-32
lines changed

6 files changed

+200
-32
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.env
22
/vendor/
33
.phpunit.result.cache
4-
composer.lock
4+
composer.lock
5+
.idea

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This repository contains the official SingleStoreDB Driver for Laravel. This dri
3030
- [Series Timestamps](#series-timestamps)
3131
- [Computed Columns](#computed-columns)
3232
- [Increment Columns without Primary Key](#increment-columns-without-primary-key)
33+
- [Full-text search](#full-text-search-using-fulltext-indexes)
3334
- [Testing](#testing)
3435
- [License](#license)
3536
- [Resources](#resources)
@@ -395,6 +396,21 @@ Schema::create('test', function (Blueprint $table) {
395396
});
396397
```
397398

399+
### Full-text search using FULLTEXT indexes
400+
401+
SingleStoreDB supports full-text search across text columns in a columnstore table using the `FULLTEXT` index type.
402+
403+
Keep in mind that `FULLTEXT` is only supported when using the `utf8_unicode_ci` collation. An exception will be thrown if you try to add the index to a column with an unsupported collation.
404+
405+
```php
406+
Schema::create('test', function (Blueprint $table) {
407+
$table->id();
408+
$table->text('first_name')->collation('utf8_unicode_ci');
409+
410+
$table->fullText(['first_name']);
411+
});
412+
```
413+
398414
## Testing
399415

400416
Execute the tests using PHPUnit
@@ -425,6 +441,7 @@ This library is licensed under the Apache 2.0 License.
425441

426442
* [SingleStore](https://singlestore.com)
427443
* [Laravel](https://laravel.com)
444+
* [Full-text search documentation](https://docs.singlestore.com/managed-service/en/reference/sql-reference/data-definition-language-ddl/create-table.html#fulltext-behavior)
428445

429446
## User agreement
430447

src/Query/Grammar.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@
77

88
class Grammar extends MySqlGrammar
99
{
10+
/**
11+
* Compile a "where fulltext" clause.
12+
*
13+
* @param \Illuminate\Database\Query\Builder $query
14+
* @param array $where
15+
* @return string
16+
*/
17+
public function whereFullText(Builder $query, $where)
18+
{
19+
$columns = $this->columnize($where['columns']);
20+
21+
$value = $this->parameter($where['value']);
22+
23+
return "MATCH ({$columns}) AGAINST ({$value})";
24+
}
25+
1026
/**
1127
* @param $column
1228
* @param $value

src/Schema/Blueprint.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace SingleStore\Laravel\Schema;
44

5+
use Exception;
56
use Illuminate\Database\Connection;
7+
use Illuminate\Database\QueryException;
68
use Illuminate\Database\Schema\Blueprint as BaseBlueprint;
79
use Illuminate\Database\Schema\Grammars\Grammar;
810
use SingleStore\Laravel\Schema\Blueprint\AddsTableFlags;
@@ -31,4 +33,24 @@ public function toSql(Connection $connection, Grammar $grammar)
3133

3234
return $this->creating() ? $this->inlineCreateIndexStatements($statements) : $statements;
3335
}
36+
37+
/**
38+
* Execute the blueprint against the database.
39+
*
40+
* @param \Illuminate\Database\Connection $connection
41+
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
42+
* @return void
43+
*/
44+
public function build(Connection $connection, Grammar $grammar)
45+
{
46+
try {
47+
parent::build($connection, $grammar);
48+
} catch (QueryException $exception) {
49+
if (str_contains($exception->getMessage(), 'FULLTEXT KEY with unsupported type')) {
50+
throw new Exception('FULLTEXT is not supported when using the utf8mb4 collation.');
51+
} else {
52+
throw $exception;
53+
}
54+
}
55+
}
3456
}

tests/Hybrid/CreateTable/MiscCreateTest.php

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace SingleStore\Laravel\Tests\Hybrid\CreateTable;
44

5-
use Illuminate\Foundation\Application;
65
use SingleStore\Laravel\Schema\Blueprint;
76
use SingleStore\Laravel\Tests\BaseTest;
87
use SingleStore\Laravel\Tests\Hybrid\HybridTestHelpers;
@@ -14,24 +13,16 @@ class MiscCreateTest extends BaseTest
1413
/** @test */
1514
public function all_keys_are_added_in_create_columnstore()
1615
{
17-
if (version_compare(Application::VERSION, '8.12.0', '<=')) {
18-
// fulltext not added until later on in laravel 8 releases
19-
$this->markTestSkipped('requires higher laravel version');
20-
21-
return;
22-
}
23-
2416
$blueprint = $this->createTable(function (Blueprint $table) {
2517
$table->string('primary')->primary('name1');
2618
$table->string('index')->index('name2');
2719
$table->string('foo');
2820
$table->index('foo', 'name3', 'hash');
29-
$table->string('fulltext')->fulltext('name5')->charset('utf8');
3021
});
3122

3223
$this->assertCreateStatement(
3324
$blueprint,
34-
'create table `test` (`primary` varchar(255) not null, `index` varchar(255) not null, `foo` varchar(255) not null, `fulltext` varchar(255) character set utf8 not null, index `name3` using hash(`foo`), primary key `name1`(`primary`), index `name2`(`index`), fulltext `name5`(`fulltext`))'
25+
'create table `test` (`primary` varchar(255) not null, `index` varchar(255) not null, `foo` varchar(255) not null, index `name3` using hash(`foo`), primary key `name1`(`primary`), index `name2`(`index`))'
3526
);
3627
}
3728

@@ -51,27 +42,6 @@ public function all_keys_are_added_in_create_rowstore()
5142
);
5243
}
5344

54-
/** @test */
55-
public function fulltext_index()
56-
{
57-
if (version_compare(Application::VERSION, '8.12.0', '<=')) {
58-
// fulltext not added until later on in laravel 8 releases
59-
$this->markTestSkipped('requires higher laravel version');
60-
61-
return;
62-
}
63-
64-
$blueprint = $this->createTable(function (Blueprint $table) {
65-
$table->string('name')->charset('utf8');
66-
$table->fullText('name', 'idx');
67-
});
68-
69-
$this->assertCreateStatement(
70-
$blueprint,
71-
'create table `test` (`name` varchar(255) character set utf8 not null, fulltext `idx`(`name`))'
72-
);
73-
}
74-
7545
/** @test */
7646
public function medium_integer_id()
7747
{

tests/Hybrid/FulltextTest.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace SingleStore\Laravel\Tests\Hybrid\Json;
4+
5+
use Illuminate\Foundation\Application;
6+
use Illuminate\Support\Facades\DB;
7+
use SingleStore\Laravel\Schema\Blueprint;
8+
use SingleStore\Laravel\Tests\BaseTest;
9+
use SingleStore\Laravel\Tests\Hybrid\HybridTestHelpers;
10+
11+
class FulltextTest extends BaseTest
12+
{
13+
use HybridTestHelpers;
14+
15+
/** @test */
16+
public function fulltext()
17+
{
18+
if (version_compare(Application::VERSION, '8.79.0', '<=')) {
19+
// fulltext not added until later on in laravel 8 releases
20+
$this->markTestSkipped('requires higher laravel version');
21+
22+
return;
23+
}
24+
25+
$query = DB::table('test')->whereFullText('title', 'performance');
26+
27+
$this->assertEquals(
28+
'select * from `test` where MATCH (`title`) AGAINST (?)',
29+
$query->toSql()
30+
);
31+
32+
$this->assertSame('performance', $query->getBindings()[0]);
33+
34+
if (! $this->runHybridIntegrations()) {
35+
return;
36+
}
37+
38+
$this->createTable(function (Blueprint $table) {
39+
$table->id();
40+
$table->text('title')->collation('utf8_unicode_ci');
41+
42+
$table->fullText(['title']);
43+
});
44+
45+
DB::table('test')->insert([[
46+
'title' => 'Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems',
47+
], [
48+
'title' => 'Data Pipelines Pocket Reference: Moving and Processing Data for Analytics',
49+
], [
50+
'title' => 'Data Quality Fundamentals',
51+
], [
52+
'title' => 'High Performance MySQL: Optimization, Backups, and Replication',
53+
]]);
54+
55+
// Force the index to be updated immediately, as it may happen async a little later
56+
DB::statement('OPTIMIZE TABLE test FLUSH');
57+
58+
$this->assertSame(
59+
'High Performance MySQL: Optimization, Backups, and Replication',
60+
$query->get()[0]->title
61+
);
62+
}
63+
64+
/** @test */
65+
public function fulltext_multicolumn()
66+
{
67+
if (version_compare(Application::VERSION, '8.79.0', '<=')) {
68+
// fulltext not added until later on in laravel 8 releases
69+
$this->markTestSkipped('requires higher laravel version');
70+
71+
return;
72+
}
73+
74+
$query = DB::table('test')->whereFullText(['name', 'race'], 'Laika');
75+
76+
$this->assertEquals(
77+
'select * from `test` where MATCH (`name`, `race`) AGAINST (?)',
78+
$query->toSql()
79+
);
80+
81+
$this->assertSame('Laika', $query->getBindings()[0]);
82+
83+
if (! $this->runHybridIntegrations()) {
84+
return;
85+
}
86+
87+
$this->createTable(function (Blueprint $table) {
88+
$table->id();
89+
$table->text('name')->collation('utf8_unicode_ci');
90+
$table->text('race')->collation('utf8_unicode_ci');
91+
92+
$table->fullText(['name', 'race']);
93+
});
94+
95+
DB::table('test')->insert([[
96+
'name' => 'Laika',
97+
'race' => 'Dog',
98+
], [
99+
'name' => 'Ham',
100+
'race' => 'Monkey',
101+
]]);
102+
103+
// Force the index to be updated immediately, as it may happen async a little later
104+
DB::statement('OPTIMIZE TABLE test FLUSH');
105+
106+
$this->assertSame('Laika', $query->get()[0]->name);
107+
}
108+
109+
/** @test */
110+
public function throws_exception_when_using_an_unsupported_collation()
111+
{
112+
if (version_compare(Application::VERSION, '8.79.0', '<=')) {
113+
// fulltext not added until later on in laravel 8 releases
114+
$this->markTestSkipped('requires higher laravel version');
115+
116+
return;
117+
}
118+
119+
if (! $this->runHybridIntegrations()) {
120+
return;
121+
}
122+
123+
try {
124+
// The default collation is utf8mb4_general_ci, which is unsupported for FULLTEXT
125+
$this->createTable(function (Blueprint $table) {
126+
$table->id();
127+
$table->text('name');
128+
129+
$table->fullText(['name']);
130+
});
131+
} catch (\Exception $exception) {
132+
$this->assertEquals(
133+
'FULLTEXT is not supported when using the utf8mb4 collation.',
134+
$exception->getMessage()
135+
);
136+
137+
return;
138+
}
139+
140+
$this->fail('Did not throw exception when using an unsupported collation');
141+
}
142+
}

0 commit comments

Comments
 (0)