4.6.x search.module do_search($keywords, $type, $join = '', $where = '1', $variation = true)
4.7.x search.module do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $select2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC')
5.x search.module do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $select2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC')
6.x search.module do_search($keywords, $type, $join1 = '', $where1 = '1 = 1', $arguments1 = array(), $columns2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC')

Do a query on the full-text search index for a word or words.

This function is normally only called by each module that support the indexed search (and thus, implements hook_update_index()).

Results are retrieved in two logical passes. However, the two passes are joined together into a single query. And in the case of most simple queries the second pass is not even used.

The first pass selects a set of all possible matches, which has the benefit of also providing the exact result set for simple "AND" or "OR" searches.

The second portion of the query further refines this set by verifying advanced text conditions (such negative or phrase matches)


$keywords: A search string as entered by the user.

$type: A string identifying the calling module.

$join1: (optional) Inserted into the JOIN part of the first SQL query. For example "INNER JOIN {node} n ON n.nid = i.sid".

$where1: (optional) Inserted into the WHERE part of the first SQL query. For example "(n.status > %d)".

$arguments1: (optional) Extra SQL arguments belonging to the first query.

$columns2: (optional) Inserted into the SELECT pat of the second query. Must contain a column selected as 'score'. defaults to 'i.relevance AS score'

$join2: (optional) Inserted into the JOIN par of the second SQL query. For example "INNER JOIN {node_comment_statistics} n ON n.nid = i.sid"

$arguments2: (optional) Extra SQL arguments belonging to the second query parameter.

$sort_parameters: (optional) SQL arguments for sorting the final results. Default: 'ORDER BY score DESC'

Return value

An array of objects for the search results.

Related topics

2 calls to do_search()
hook_search in developer/hooks/core.php
Define a custom search routine.
node_search in modules/node/node.module
Implementation of hook_search().


modules/search/search.module, line 917
Enables site-wide keyword searching.


function do_search($keywords, $type, $join1 = '', $where1 = '1 = 1', $arguments1 = array(), $columns2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC') {
  $query = search_parse_query($keywords);

  if ($query[2] == '') {
    form_set_error('keys', t('You must include at least one positive keyword with @count characters or more.', array('@count' => variable_get('minimum_word_size', 3))));
  if ($query[6]) {
    if ($query[6] == 'or') {
      drupal_set_message(t('Search for either of the two terms with uppercase <strong>OR</strong>. For example, <strong>cats OR dogs</strong>.'));
  if ($query === NULL || $query[0] == '' || $query[2] == '') {
    return array();

  // Build query for keyword normalization.
  $conditions = "$where1 AND ($query [2]) AND i.type = '%s'";
  $arguments1 = array_merge($arguments1, $query[3], array($type));
  $join = "INNER JOIN {search_total} t ON i.word = t.word $join1";
  if (!$query[5]) {
    $conditions .= " AND ($query [0])";
    $arguments1 = array_merge($arguments1, $query[1]);
    $join .= " INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type";

  // Calculate maximum keyword relevance, to normalize it.
  $select = "SELECT SUM(i.score * t.count) AS score FROM {search_index} i $join WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d ORDER BY score DESC";
  $arguments = array_merge($arguments1, array($query[4]));
  $normalize = db_result(db_query_range($select, $arguments, 0, 1));
  if (!$normalize) {
    return array();
  $columns2 = str_replace('i.relevance', '(' . (1.0 / $normalize) . ' * SUM(i.score * t.count))', $columns2);

  // Build query to retrieve results.
  $select = "SELECT i.type, i.sid, $columns2 FROM {search_index} i $join $join2 WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d";
  $count_select = "SELECT COUNT(*) FROM ($select) n1";
  $arguments = array_merge($arguments2, $arguments1, array($query[4]));

  // Do actual search query
  $result = pager_query("$select $sort_parameters", 10, 0, $count_select, $arguments);
  $results = array();
  while ($item = db_fetch_object($result)) {
    $results[] = $item;
  return $results;


adamgerbert’s picture

I believe the documentation on this function to be poor, and have filed a bug report. For people who are having trouble utilizing the return value of do_search, please see http://drupal.org/node/993834

Here is a small piece of example code on utilizing do_search():

$searchResults = do_search($searchString, "node");
foreach($searchResults as $result)
   //the 'sid' is actually a node id.
   $nid = $result->sid;
   //do whatever you want with the node ID

It also appears that the search text snippet (the text you see under the node title when searching using Drupals search form) is in the 'search_dataset table' keyed by the 'sid' you get from do_search().

laceysanderson’s picture

This function will only every return at most 10 results. This is due to the hard-coding of 10 as the limit in the pager_query (near bottom -2nd parameter). In order to have a search function which returns more then 10 or *gasp* all results:

1. Create your own module with a custom search function.
2. Copy the code from do_search into your custom search function.
3. Add a $limit parameter with default 10 to the function parameters
4. Then replace 10 with $limit in the pager_query.

If you want all results returned (for example, when implementing a web-service) simply replace
$result = pager_query("$select $sort_parameters", 10, 0, $count_select, $arguments);
$result = db_query("$select $sort_parameters", $arguments);
in your custom search function.

memcinto’s picture

How then do you get Drupal to use your custom search function instead of its own? do_search isn't a hook. . .

thecoolestguy’s picture

One way to do it is like this:

  1. Copy (don't move) the core search module folder from /modules/search to /sites/all/modules/custom/search
  2. When it's copied over, go to your modules page and disable the search module, then re-enable it (this will allow you to make changes to the code that's in there without hacking core)
  3. In the 'new' search module folder (/sites/all/modules/search), make the changes suggested above to the do_search() function found withing search.module -- personally, I just did the replacement of the one pager_query line with the following:
    $result = db_query("$select $sort_parameters", $arguments);
  4. No more paging! (but get ready for some latency!)
EPO’s picture

This coding is so feeble and annoying that a real bug can hardly compete. A search prequery hook wouldn't be a big issue and I demand it insistently.

hyperlinked’s picture

Be warned that do_search() does not obey Drupal's node_access permissions. If you disallow anonymous users or another user type from accessing a certain type of node, anyone will be able to use the search to see previews of that node type. Once they click on the result, they'll get an error message saying that they can't access that node.

You'll either need to augment your $where1 and $join1 statements to check permission tables or perform a node_access() check on each result returned from do_search(). The latter approach has a drawback in that the pager query doesn't know if your result set is being truncated so it'll always show the same number of pages on the result page regardless of how many actual results the current user is able to see. It's also possible for an entire page to be missing because all 10 of the results from page 3 might be invisible to the current user and that user will get a confusing "No Results - Blue Smurf" response even though page 4 might be full of results.

rooby’s picture

Yes, it is up to the caller of do_search() to handle access control. For an example see node_search(), which uses _db_rewrite_sql() to add node access to its where & join clauses.