@@ -61,6 +61,34 @@ public function search(Builder $builder)
6161 ];
6262 }
6363
64+ /**
65+ * Get the Eloquent models for the given builder.
66+ *
67+ * @param \Laravel\Scout\Builder $builder
68+ * @param int|null $page
69+ * @param int|null $perPage
70+ * @return \Illuminate\Database\Eloquent\Collection
71+ */
72+ protected function searchModels (Builder $ builder , $ page = null , $ perPage = null )
73+ {
74+ return $ this ->buildSearchQuery ($ builder )
75+ ->when (! is_null ($ page ) && ! is_null ($ perPage ), function ($ query ) use ($ page , $ perPage ) {
76+ $ query ->forPage ($ page , $ perPage );
77+ })
78+ ->when ($ builder ->orders , function ($ query ) use ($ builder ) {
79+ foreach ($ builder ->orders as $ order ) {
80+ $ query ->orderBy ($ order ['column ' ], $ order ['direction ' ]);
81+ }
82+ })
83+ ->when (! $ this ->getFullTextColumns ($ builder ), function ($ query ) use ($ builder ) {
84+ $ query ->orderBy ($ builder ->model ->getTable ().'. ' .$ builder ->model ->getScoutKeyName (), 'desc ' );
85+ })
86+ ->when ($ this ->shouldOrderByRelevance ($ builder ), function ($ query ) use ($ builder ) {
87+ $ this ->orderByRelevance ($ builder , $ query );
88+ })
89+ ->get ();
90+ }
91+
6492 /**
6593 * Paginate the given search on the engine.
6694 *
@@ -94,6 +122,9 @@ public function paginateUsingDatabase(Builder $builder, $perPage, $pageName, $pa
94122 ->when (! $ this ->getFullTextColumns ($ builder ), function ($ query ) use ($ builder ) {
95123 $ query ->orderBy ($ builder ->model ->getTable ().'. ' .$ builder ->model ->getScoutKeyName (), 'desc ' );
96124 })
125+ ->when ($ this ->shouldOrderByRelevance ($ builder ), function ($ query ) use ($ builder ) {
126+ $ this ->orderByRelevance ($ builder , $ query );
127+ })
97128 ->paginate ($ perPage , ['* ' ], $ pageName , $ page );
98129 }
99130
@@ -129,32 +160,10 @@ public function simplePaginateUsingDatabase(Builder $builder, $perPage, $pageNam
129160 ->when (! $ this ->getFullTextColumns ($ builder ), function ($ query ) use ($ builder ) {
130161 $ query ->orderBy ($ builder ->model ->getTable ().'. ' .$ builder ->model ->getScoutKeyName (), 'desc ' );
131162 })
132- ->simplePaginate ($ perPage , ['* ' ], $ pageName , $ page );
133- }
134-
135- /**
136- * Get the Eloquent models for the given builder.
137- *
138- * @param \Laravel\Scout\Builder $builder
139- * @param int|null $page
140- * @param int|null $perPage
141- * @return \Illuminate\Database\Eloquent\Collection
142- */
143- protected function searchModels (Builder $ builder , $ page = null , $ perPage = null )
144- {
145- return $ this ->buildSearchQuery ($ builder )
146- ->when (! is_null ($ page ) && ! is_null ($ perPage ), function ($ query ) use ($ page , $ perPage ) {
147- $ query ->forPage ($ page , $ perPage );
148- })
149- ->when ($ builder ->orders , function ($ query ) use ($ builder ) {
150- foreach ($ builder ->orders as $ order ) {
151- $ query ->orderBy ($ order ['column ' ], $ order ['direction ' ]);
152- }
153- })
154- ->when (! $ this ->getFullTextColumns ($ builder ), function ($ query ) use ($ builder ) {
155- $ query ->orderBy ($ builder ->model ->getTable ().'. ' .$ builder ->model ->getScoutKeyName (), 'desc ' );
163+ ->when ($ this ->shouldOrderByRelevance ($ builder ), function ($ query ) use ($ builder ) {
164+ $ this ->orderByRelevance ($ builder , $ query );
156165 })
157- ->get ( );
166+ ->simplePaginate ( $ perPage , [ ' * ' ], $ pageName , $ page );
158167 }
159168
160169 /**
@@ -196,9 +205,11 @@ protected function initializeSearchQuery(Builder $builder, array $columns, array
196205 return $ query ;
197206 }
198207
199- return $ query ->where (function ($ query ) use ($ builder , $ columns , $ prefixColumns , $ fullTextColumns ) {
200- $ connectionType = $ builder ->model ->getConnection ()->getDriverName ();
208+ [$ connectionType ] = [
209+ $ builder ->modelConnectionType (),
210+ ];
201211
212+ return $ query ->where (function ($ query ) use ($ connectionType , $ builder , $ columns , $ prefixColumns , $ fullTextColumns ) {
202213 $ canSearchPrimaryKey = ctype_digit ($ builder ->query ) &&
203214 in_array ($ builder ->model ->getKeyType (), ['int ' , 'integer ' ]) &&
204215 ($ connectionType != 'pgsql ' || $ builder ->query <= PHP_INT_MAX ) &&
@@ -212,11 +223,7 @@ protected function initializeSearchQuery(Builder $builder, array $columns, array
212223
213224 foreach ($ columns as $ column ) {
214225 if (in_array ($ column , $ fullTextColumns )) {
215- $ query ->orWhereFullText (
216- $ builder ->model ->qualifyColumn ($ column ),
217- $ builder ->query ,
218- $ this ->getFullTextOptions ($ builder )
219- );
226+ continue ;
220227 } else {
221228 if ($ canSearchPrimaryKey && $ column === $ builder ->model ->getScoutKeyName ()) {
222229 continue ;
@@ -229,9 +236,60 @@ protected function initializeSearchQuery(Builder $builder, array $columns, array
229236 );
230237 }
231238 }
239+
240+ if (count ($ fullTextColumns ) > 0 ) {
241+ $ query ->orWhereFullText (
242+ array_map (fn ($ column ) => $ builder ->model ->qualifyColumn ($ column ), $ fullTextColumns ),
243+ $ builder ->query ,
244+ $ this ->getFullTextOptions ($ builder )
245+ );
246+ }
232247 });
233248 }
234249
250+ /**
251+ * Determine if the query should be ordered by relevance.
252+ */
253+ protected function shouldOrderByRelevance (Builder $ builder ): bool
254+ {
255+ // MySQL orders by relevance by default, so we will only order by relevance on
256+ // Postgres with no developer-defined orders. If there is developer defined
257+ // order by clauses we will let those take precedence over the relevance.
258+ return $ builder ->modelConnectionType () === 'pgsql ' &&
259+ count ($ this ->getFullTextColumns ($ builder )) > 0 &&
260+ empty ($ builder ->orders );
261+ }
262+
263+ /**
264+ * Add an "order by" clause that orders by relevance (Postgres only).
265+ *
266+ * @param \Laravel\Scout\Builder $builder
267+ * @param \Illuminate\Database\Eloquent\Builder $query
268+ * @return \Illuminate\Database\Eloquent\Builder
269+ */
270+ protected function orderByRelevance (Builder $ builder , $ query )
271+ {
272+ $ fullTextColumns = $ this ->getFullTextColumns ($ builder );
273+
274+ $ language = $ this ->getFullTextOptions ($ builder )['language ' ] ?? 'english ' ;
275+
276+ $ vectors = collect ($ fullTextColumns )->map (function ($ column ) use ($ builder , $ language ) {
277+ return sprintf ("to_tsvector('%s', %s) " , $ language , $ builder ->model ->qualifyColumn ($ column ));
278+ })->implode (' || ' );
279+
280+ return $ query ->orderByRaw (
281+ sprintf (
282+ 'ts_rank( ' .$ vectors .', %s(?)) desc ' ,
283+ match ($ this ->getFullTextOptions ($ builder )['mode ' ] ?? 'plainto_tsquery ' ) {
284+ 'phrase ' => 'phraseto_tsquery ' ,
285+ 'websearch ' => 'websearch_to_tsquery ' ,
286+ default => 'plainto_tsquery ' ,
287+ },
288+ ),
289+ [$ builder ->query ]
290+ );
291+ }
292+
235293 /**
236294 * Add additional, developer defined constraints to the search query.
237295 *
0 commit comments