Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FullText Search in laravel-oci8 #800

Open
wants to merge 21 commits into
base: 10.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
de9568e
feat: whereFullText query grammar
jonas-elias Aug 3, 2023
5b76c5b
feat: compileFullText index schema grammar
jonas-elias Aug 3, 2023
042cfc8
feat-test: whereFullText query
jonas-elias Aug 3, 2023
bbe901e
feat-test: create index fullText in schema
jonas-elias Aug 3, 2023
a36b316
fix-test: change name statement whereFullText
jonas-elias Aug 3, 2023
30523ef
fix: styleci integration yajra/laravel-oci8
jonas-elias Aug 3, 2023
505cd8e
fix: grammar whereFullText multiple parameters with logical operator
jonas-elias Aug 4, 2023
bde355e
fix-test: whereFullText with single and multiple parameters
jonas-elias Aug 4, 2023
1a30ba6
fix: styleci integration yajra/laravel-oci8
jonas-elias Aug 4, 2023
e796371
fix: removed of contains() oracle method
jonas-elias Aug 4, 2023
6107157
feat-test: orWhereFullText added in query grammar tests
jonas-elias Aug 4, 2023
d14b070
fix: labelSearch auto increment in contains() oracle feat
jonas-elias Aug 4, 2023
84d4562
fix-test: third parameter in contains() oracle feat
jonas-elias Aug 4, 2023
e218712
fix: style integration yajra/laravel-oci8
jonas-elias Aug 4, 2023
abac01e
fix-style: integration yajra/laravel-oci8
jonas-elias Aug 4, 2023
7588aff
feat: oracle ctx_ddl preferences implemented
jonas-elias Aug 11, 2023
6fafdc1
feat-test: oracle ctx_ddl preferences tests
jonas-elias Aug 11, 2023
da97695
fix-test: dropFullText items single and multiples
jonas-elias Aug 11, 2023
61bdcfd
feat: dropFullText method implemented && fix fullText create single a…
jonas-elias Aug 11, 2023
5fb284a
feat: ctxDdlPreferences attribute created to call modifications commands
jonas-elias Aug 11, 2023
8272ef4
fix-ci: style yajra/laravel-oci8
jonas-elias Aug 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/Oci8/Query/Grammars/OracleGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class OracleGrammar extends Grammar
*/
protected $maxLength;

/**
* @var int
*/
protected $labelSearchFullText = 1;

/**
* Compile a delete statement with joins into SQL.
*
Expand Down Expand Up @@ -597,6 +602,40 @@ protected function whereInRaw(Builder $query, $where)
return '0 = 1';
}

/**
* Compile a "where fulltext" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
public function whereFullText(Builder $query, $where)
{
// Build the fullText clause
$fullTextClause = collect($where['columns'])
->map(function ($column, $index) use ($where) {
$labelSearchFullText = $index > 0 ? ++$this->labelSearchFullText : $this->labelSearchFullText;

return "CONTAINS({$this->wrap($column)}, {$this->parameter($where['value'])}, {$labelSearchFullText}) > 0";
})
->implode(" {$where['boolean']} ");

// Count the total number of columns in the clauses
$fullTextClauseCount = array_reduce($query->wheres, function ($count, $queryWhere) {
return $queryWhere['type'] === 'Fulltext' ? $count + count($queryWhere['columns']) : $count;
}, 0);

// Reset the counter if all columns were used in the clause
if ($fullTextClauseCount === $this->labelSearchFullText) {
$this->labelSearchFullText = 0;
}

// Increment the counter for the next clause
$this->labelSearchFullText++;

return $fullTextClause;
}

private function resolveClause($column, $values, $type)
{
$chunks = array_chunk($values, 1000);
Expand Down
20 changes: 19 additions & 1 deletion src/Oci8/Schema/Grammars/OracleGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,24 @@ public function compileIndex(Blueprint $blueprint, Fluent $command)
return "create index {$command->index} on ".$this->wrapTable($blueprint).' ( '.$this->columnize($command->columns).' )';
}

/**
* Compile a fulltext index key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileFullText(Blueprint $blueprint, Fluent $command): string
{
$indexName = $command->index;
$tableName = $this->wrapTable($blueprint);
$columns = $this->columnize($command->columns);

$query = "create index $indexName on $tableName ($columns) indextype is ctxsys.context parameters ('sync(on commit)')";

return $query;
}

/**
* Compile a drop table command.
*
Expand All @@ -326,7 +344,7 @@ public function compileDrop(Blueprint $blueprint, Fluent $command)
public function compileDropAllTables()
{
return 'BEGIN
FOR c IN (SELECT table_name FROM user_tables) LOOP
FOR c IN (SELECT table_name FROM user_tables WHERE secondary = \'N\') LOOP
EXECUTE IMMEDIATE (\'DROP TABLE "\' || c.table_name || \'" CASCADE CONSTRAINTS\');
END LOOP;

Expand Down
43 changes: 43 additions & 0 deletions tests/Database/Oci8QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,49 @@ public function testArrayWhereColumn()
$this->assertEquals([], $builder->getBindings());
}

public function testWhereFullTextWithSingleParameter()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->whereFullText('name', 'johnny');
$this->assertSame('select * from "USERS" where CONTAINS("NAME", ?, 1) > 0', $builder->toSql());
$this->assertEquals(['johnny'], $builder->getBindings());
}

public function testWhereFullTextWithMultipleParameters()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->whereFullText(['firstname', 'lastname'], 'johnny');
$this->assertSame('select * from "USERS" where CONTAINS("FIRSTNAME", ?, 1) > 0 and CONTAINS("LASTNAME", ?, 2) > 0',
$builder->toSql());
$this->assertEquals(['johnny'], $builder->getBindings());
}

public function testWhereFullTextWithLogicalOrOperator()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->whereFullText(['firstname', 'lastname'], 'johnny', [], 'or');
$this->assertSame('select * from "USERS" where CONTAINS("FIRSTNAME", ?, 1) > 0 or CONTAINS("LASTNAME", ?, 2) > 0',
$builder->toSql());
$this->assertEquals(['johnny'], $builder->getBindings());
}

public function testOrWhereFullTextWithSingleParameter()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->orWhereFullText('firstname', 'johnny');
$this->assertSame('select * from "USERS" where CONTAINS("FIRSTNAME", ?, 1) > 0', $builder->toSql());
$this->assertEquals(['johnny'], $builder->getBindings());
}

public function testOrWhereFullTextWithMultipleParameters()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->orWhereFullText('firstname', 'johnny')->orWhereFullText('lastname', 'white');
$this->assertSame('select * from "USERS" where CONTAINS("FIRSTNAME", ?, 1) > 0 or CONTAINS("LASTNAME", ?, 2) > 0',
$builder->toSql());
$this->assertEquals(['johnny', 'white'], $builder->getBindings());
}

public function testUnions()
{
$builder = $this->getBuilder();
Expand Down
13 changes: 12 additions & 1 deletion tests/Database/Oci8SchemaGrammarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,17 @@ public function testAddingIndex()
$this->assertEquals('create index baz on users ( foo, bar )', $statements[0]);
}

public function testAddingFullTextIndex()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL generated is as expected but doesn't work when executed on actual DB migration.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg.. multiple columns index in oracle is implemented with ctx_ddl preferences 😅

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, ctx_ddl is something new to me 😅. Do I need some special setup for this work?

Will also try to research this. Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is new for me too 😅, but i researched about this, is simple to use.

For create multiple columns index, is necessary execute grant CTX_DDL to owner.

The code create preference "idx" and set configuration for columns "FIRSTNAME" and "LASTNAME", attribuiting in creation of index "firstname_multiple":

BEGIN
    ctx_ddl.create_preference('idx', 'MULTI_COLUMN_DATASTORE');
    ctx_ddl.set_attribute('idx', 'COLUMNS', 'FIRSTNAME, LASTNAME');
    EXECUTE IMMEDIATE 'create index firstname_multiple ON "USERS" ("FIRSTNAME") INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS (''DATASTORE idx SYNC(ON COMMIT)'')';
END;

Is possible search if content contains in column "LASTNAME" by:
SELECT * FROM USERS u WHERE CONTAINS(FIRSTNAME, 'content_lastname') > 0

Summarized implementation of multiple full text index columns in oracle 😎

References:
https://docs.oracle.com/en/database/oracle/oracle-database/19/ccref/CTX_DDL-package.html#GUID-0F7C39E8-E44A-421C-B40D-3B3578B507E9

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this bug and implemented ctx_ddl preferences to create multi_columns_index oracle feature. In method compileFullText(), i implemented one index to each column, enabling the search of multi column preference. If you have any suggestions, don't hesitate to help me 😅. Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @hpacleb, you have a new suggestions? 😁

{
$blueprint = new Blueprint('users');
$blueprint->fullText(['firstname', 'lastname'], 'name');
$statements = $blueprint->toSql($this->getConnection(), $this->getGrammar());

$this->assertEquals(1, count($statements));

$this->assertEquals("create index name on users (firstname, lastname) indextype is ctxsys.context parameters ('sync(on commit)')", $statements[0]);
}

public function testAddingForeignKey()
{
$blueprint = new Blueprint('users');
Expand Down Expand Up @@ -877,7 +888,7 @@ public function testDropAllTables()
$statement = $this->getGrammar()->compileDropAllTables();

$expected = 'BEGIN
FOR c IN (SELECT table_name FROM user_tables) LOOP
FOR c IN (SELECT table_name FROM user_tables WHERE secondary = \'N\') LOOP
EXECUTE IMMEDIATE (\'DROP TABLE "\' || c.table_name || \'" CASCADE CONSTRAINTS\');
END LOOP;

Expand Down
Loading