From 0f7fca47c1bd3af313783723e376ee523065f4b8 Mon Sep 17 00:00:00 2001 From: Aaron Huisinga Date: Thu, 18 Jan 2024 12:44:08 -0600 Subject: [PATCH] Add support for Microsoft SQL --- src/Searchable.php | 59 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/Searchable.php b/src/Searchable.php index 66c9b07..37598d1 100644 --- a/src/Searchable.php +++ b/src/Searchable.php @@ -71,13 +71,14 @@ trait Searchable * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $search + * @param int $limit * @param string|null|Closure $restriction * * @return \Illuminate\Database\Eloquent\Builder * * @see search() */ - public function scopeSearch(Builder $query, $search, $restriction = null) + public function scopeSearch(Builder $query, $search, $limit = 25, $restriction = null) { $this->searchable = $this->toSearchableArray(); @@ -110,7 +111,7 @@ public function scopeSearch(Builder $query, $search, $restriction = null) } $this->addSelectsToQuery($cloned_query, $selects); - $this->filterQueryWithRelevance($cloned_query, $relevance_count); + $this->filterQueryWithRelevance($cloned_query, $relevance_count, $limit); $this->makeGroupBy($cloned_query); if ($restriction instanceof Closure) { @@ -126,6 +127,10 @@ public function scopeSearch(Builder $query, $search, $restriction = null) $this->mergeQueries($cloned_query, $query); + if ($this->getDatabaseDriver() === 'sqlsrv') { + $query->whereRaw('CAST(relevance AS NUMERIC(10,2)) >= ' . number_format($this->threshold, 2, '.', '')); + } + return $query; } @@ -224,7 +229,7 @@ protected function getMatchers() unset($matchers['exactFullMatcher'], $matchers['exactInStringMatcher']); } - if ($this->getDatabaseDriver() === 'sqlite') { + if (in_array($this->getDatabaseDriver(), ['sqlite', 'sqlsrv'], true)) { unset($matchers['similarStringMatcher']); } @@ -243,6 +248,20 @@ protected function getDatabaseDriver() return config('database.connections.' . $key . '.driver'); } + /** + * Returns the delimiter to use for the current database driver. + * + * @return string + */ + protected function getDelimiter() + { + if ($this->getDatabaseDriver() === 'sqlsrv') { + return '"'; + } + + return '`'; + } + /** * Returns the search columns. * @@ -347,7 +366,7 @@ protected function getSearchQuery($column, $relevance) */ protected function createMatcher($column, $relevance) { - $formatted = '`' . str_replace('.', '`.`', $column) . '`'; + $formatted = $this->getDelimiter() . str_replace('.', "{$this->getDelimiter()}.{$this->getDelimiter()}", $column) . $this->getDelimiter(); if (isset($this->searchable['mutations'][$column])) { $formatted = $this->searchable['mutations'][$column] . '(' . $formatted . ')'; @@ -406,9 +425,9 @@ protected function getFullTextColumns() protected function fullTextMatcher($column, $relevance) { $this->search_bindings[] = implode(' ', $this->orderedWords); - $column = str_replace('.', '`.`', $column); + $column = str_replace('.', "{$this->getDelimiter()}.{$this->getDelimiter()}", $column); - $this->matcherQuery = "(MATCH(`$column`) AGAINST (?) * $relevance * 2)"; + $this->matcherQuery = "(MATCH({$this->getDelimiter()}$column{$this->getDelimiter()}) AGAINST (?) * $relevance * 2)"; } /** @@ -427,12 +446,20 @@ protected function addSelectsToQuery(Builder $query, array $selects) * * @param \Illuminate\Database\Eloquent\Builder $query * @param int $relevance_count + * @param int|null $limit */ - protected function filterQueryWithRelevance(Builder $query, $relevance_count) + protected function filterQueryWithRelevance(Builder $query, $relevance_count, $limit) { + if ($limit === null) { + $limit = 25; + } + $this->threshold = $relevance_count * (count($this->getColumns()) / 6) * (count($this->getMatchers()) / 6); - $query->havingRaw('relevance >= ' . number_format($this->threshold, 2, '.', '')); + $query->limit($limit); + if ($this->getDatabaseDriver() !== 'sqlsrv') { + $query->havingRaw('relevance >= ' . number_format($this->threshold, 2, '.', '')); + } $query->orderBy('relevance', 'desc'); } @@ -443,6 +470,10 @@ protected function filterQueryWithRelevance(Builder $query, $relevance_count) */ protected function makeGroupBy(Builder $query) { + if ($this->getDatabaseDriver() === 'sqlsrv') { + return; + } + if ($groupBy = $this->getGroupBy()) { $query->groupBy($groupBy); } else { @@ -562,9 +593,9 @@ protected function consecutiveCharactersMatcher($query, $column, $relevance) $this->searchString = '%' . implode('%', str_split(preg_replace('/[^0-9a-zA-Z]/', '', $query))) . '%'; $this->search_bindings[] = $this->searchString; $this->search_bindings[] = $query; - $column = str_replace('.', '`.`', $column); + $column = str_replace('.', "{$this->getDelimiter()}.{$this->getDelimiter()}", $column); - $this->matcherQuery = "CASE WHEN REPLACE(`$column`, '\.', '') $this->operator ? THEN ROUND($relevance * ( {$this->getLengthMethod()}( ? ) / {$this->getLengthMethod()}( REPLACE(`$column`, ' ', '') ))) ELSE 0 END"; + $this->matcherQuery = "CASE WHEN REPLACE({$this->getDelimiter()}$column{$this->getDelimiter()}, '\.', '') $this->operator ? THEN ROUND($relevance * ( {$this->getLengthMethod()}( ? ) / {$this->getLengthMethod()}( REPLACE({$this->getDelimiter()}$column{$this->getDelimiter()}, ' ', '') )), 0) ELSE 0 END"; } /** @@ -578,6 +609,10 @@ protected function getLengthMethod() return 'LENGTH'; } + if ($this->getDatabaseDriver() === 'sqlsrv') { + return 'LEN'; + } + return 'CHAR_LENGTH'; } @@ -626,9 +661,9 @@ protected function timesInStringMatcher($query, $column, $relevance) { $this->search_bindings[] = $query; $this->search_bindings[] = $query; - $column = str_replace('.', '`.`', $column); + $column = str_replace('.', "{$this->getDelimiter()}.{$this->getDelimiter()}", $column); - $this->matcherQuery = "($relevance * ROUND(({$this->getLengthMethod()}(COALESCE(`$column`, '')) - {$this->getLengthMethod()}( REPLACE( LOWER(COALESCE(`$column`, '')), lower(?), ''))) / LENGTH(?)))"; + $this->matcherQuery = "($relevance * ROUND(({$this->getLengthMethod()}(COALESCE({$this->getDelimiter()}$column{$this->getDelimiter()}, '')) - {$this->getLengthMethod()}( REPLACE( LOWER(COALESCE({$this->getDelimiter()}$column{$this->getDelimiter()}, '')), lower(?), ''))) / {$this->getLengthMethod()}(?), 0))"; } /**