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 all 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
65 changes: 64 additions & 1 deletion src/Oci8/Schema/Grammars/OracleGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,42 @@ 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
{
$tableName = $this->wrapTable($blueprint);
$columns = $command->columns;
$indexBaseName = $command->index;
$preferenceName = $indexBaseName.'_preference';

$sqlStatements = [];

foreach ($columns as $key => $column) {
$indexName = $indexBaseName;
$parametersIndex = '';

if (count($columns) > 1) {
$indexName .= "_{$key}";
$parametersIndex = "datastore {$preferenceName} ";
}

$parametersIndex .= 'sync(on commit)';

$sql = "execute immediate 'create index {$indexName} on $tableName ($column) indextype is
ctxsys.context parameters (''$parametersIndex'')';";

$sqlStatements[] = $sql;
}

return 'begin '.implode(' ', $sqlStatements).' end;';
}

/**
* Compile a drop table command.
*
Expand All @@ -326,7 +362,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 Expand Up @@ -440,6 +476,33 @@ public function compileDropForeign(Blueprint $blueprint, Fluent $command)
return $this->dropConstraint($blueprint, $command, 'foreign');
}

/**
* Compile a drop fulltext index command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileDropFullText(Blueprint $blueprint, Fluent $command): string
{
$columns = $command->columns;

if (empty($columns)) {
return $this->compileDropIndex($blueprint, $command);
}

$columns = array_map(function ($column) {
return "'".strtoupper($column)."'";
}, $columns);
$columns = implode(', ', $columns);

$dropFullTextSql = "for idx_rec in (select idx_name from ctx_user_indexes where idx_text_name in ($columns)) loop
execute immediate 'drop index ' || idx_rec.idx_name;
end loop;";

return "begin $dropFullTextSql end;";
}

/**
* Compile a rename table command.
*
Expand Down
11 changes: 11 additions & 0 deletions src/Oci8/Schema/OracleBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class OracleBuilder extends Builder
*/
public $comment;

/**
* @var \Yajra\Oci8\Schema\OraclePreferences
*/
public $ctxDdlPreferences;

/**
* @param Connection $connection
*/
Expand All @@ -26,6 +31,7 @@ public function __construct(Connection $connection)
parent::__construct($connection);
$this->helper = new OracleAutoIncrementHelper($connection);
$this->comment = new Comment($connection);
$this->ctxDdlPreferences = new OraclePreferences($connection);
}

/**
Expand All @@ -43,6 +49,8 @@ public function create($table, Closure $callback)

$callback($blueprint);

$this->ctxDdlPreferences->createPreferences($blueprint);

$this->build($blueprint);

$this->comment->setComments($blueprint);
Expand Down Expand Up @@ -99,6 +107,7 @@ public function table($table, Closure $callback)
public function drop($table)
{
$this->helper->dropAutoIncrementObjects($table);
$this->ctxDdlPreferences->dropPreferencesByTable($table);
parent::drop($table);
}

Expand All @@ -109,6 +118,7 @@ public function drop($table)
*/
public function dropAllTables()
{
$this->ctxDdlPreferences->dropAllPreferences();
$this->connection->statement($this->grammar->compileDropAllTables());
}

Expand All @@ -121,6 +131,7 @@ public function dropAllTables()
public function dropIfExists($table)
{
$this->helper->dropAutoIncrementObjects($table);
$this->ctxDdlPreferences->dropPreferencesByTable($table);
parent::dropIfExists($table);
}

Expand Down
151 changes: 151 additions & 0 deletions src/Oci8/Schema/OraclePreferences.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace Yajra\Oci8\Schema;

use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;

/**
* @see https://docs.oracle.com/en/database/oracle/oracle-database/19/ccref/CTX_DDL-package.html#GUID-0F7C39E8-E44A-421C-B40D-3B3578B507E9
*/
class OraclePreferences
{
/**
* @var \Illuminate\Database\Connection
*/
protected $connection;

/**
* @var array
*/
protected array $columns = [];

/**
* @var array
*/
protected array $preferenceName = [];

/**
* Constructor method.
*
* @return void
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}

/**
* Create a preferences values to use in index fullText.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return null
*/
public function createPreferences(Blueprint $blueprint): void
{
$this->setPreferenceFullText($blueprint);

$sql = $this->generateSqlCreatePreferences();

if (! empty($sql)) {
$this->connection->statement(
"BEGIN $sql END;"
);
}
}

/**
* Generate script sql to create preferences.
*
* @param ?string $objectNameOracle
* @param ?string $attributeNameOracle
* @return string
*/
protected function generateSqlCreatePreferences(
?string $objectNameOracle = 'MULTI_COLUMN_DATASTORE',
?string $attributeNameOracle = 'COLUMNS'
): string {
$ctxDdlCreatePreferences = [];

foreach ($this->columns as $key => $columns) {
$preferenceName = $this->preferenceName[$key];
$formattedColumns = $this->formatMultipleCtxColumns($columns);

$ctxDdlCreatePreferences[] = "ctx_ddl.create_preference('{$preferenceName}', '{$objectNameOracle}');
ctx_ddl.set_attribute('{$preferenceName}', '{$attributeNameOracle}', '{$formattedColumns}');";
}

return implode(' ', $ctxDdlCreatePreferences);
}

/**
* Set columns and preference name to class attributes.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return void
*/
public function setPreferenceFullText(Blueprint $blueprint): void
{
$this->columns = [];
$this->preferenceName = [];

foreach ($blueprint->getCommands() as $value) {
if ($value['name'] === 'fulltext' && count($value['columns']) > 1) {
$this->columns[] = $value['columns'];
$this->preferenceName[] = $value['index'].'_preference';
}
}
}

/**
* Format with "implode" function columns to use in preferences.
*
* @param array $columns
* @return string
*/
protected function formatMultipleCtxColumns(array $columns): string
{
return implode(', ', $columns);
}

/**
* Drop preferences by specified table.
*
* @param string $table
* @return void
*/
public function dropPreferencesByTable(string $table): void
{
$sqlDropPreferencesByTable = "BEGIN
FOR c IN (select distinct (substr(cui.idx_name, 1, instr(cui.idx_name, '_', -1, 1) - 1) || '_preference') preference
from
ctxsys.ctx_user_indexes cui
where
cui.idx_table = ?) LOOP
EXECUTE IMMEDIATE 'BEGIN ctx_ddl.drop_preference(:preference); END;'
USING c.preference;
END LOOP;
END;";

$this->connection->statement($sqlDropPreferencesByTable, [
strtoupper($table),
]);
}

/**
* Drop all user preferences.
*
* @return void
*/
public function dropAllPreferences(): void
{
$sqlDropAllPreferences = "BEGIN
FOR c IN (SELECT pre_name FROM ctx_user_preferences) LOOP
EXECUTE IMMEDIATE 'BEGIN ctx_ddl.drop_preference(:pre_name); END;'
USING c.pre_name;
END LOOP;
END;";

$this->connection->statement($sqlDropAllPreferences);
}
}
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
Loading
Loading