Drupal Planet

One of the things I've had to do recently is set up a development server that has multiple sites with Apache Solr so that each site has its own index in Solr.  After searching the internets for information on how to do this, I found two sources that contained part of the process, but not put together in a way that worked for me. 

    Nick Veenhof's blog post on setting up Solr on Ubuntu 9.10
    The "Apache Solr Multi-core Setup" handbook page on drupal.org (adapted from a post by Chris Rikli)

Nick's post got me through the Tomcat setup with Solr for one site, but his step for multi-site involved making copies of the solr.war file. I noticed Peter Wolanin's comment that it would be better to use the solr multi core feature to serve the 2 indexes at different paths, so I decided to try and implement it that way.  I found the handbook page, but it was not written for Tomcat on Ubuntu, so I had to combine them.  And then of course, I knew I had to blog about it.

All of these steps (except for a couple minor modifications) are copied from the two sources listed above.  I am doing this on Ubuntu 9.10, the same as Nick.

So here we go.  Please feel free to raise your hand if you have any questions.
 

Step 1. Installing Tomcat

sudo apt-get update<br />
        sudo apt-get upgrade<br />
        sudo apt-get install tomcat6 tomcat6-admin tomcat6-common tomcat6-user tomcat6-docs tomcat6-examples

This command downloads and install the following packages

tomcat6 : Servlet and JSP engine<br />
        tomcat6-admin : Admin web applications<br />
        tomcat6-common : Common files<br />
        tomcat6-user : Tools to create user instances<br />
        tomcat6-docs : Example web applications<br />
        tomcat6-examples : Example web applications

Start tomcat by typing

/etc/init.d/tomcat6 start

Step 2. Security

If you are using IP tables and installing Apache Solr on an external server, modify or add the following line to accept the port 8080:

-A INPUT -p tcp -m tcp --dport 8080 -j ACCEPT

After installation go to http://localhost:8080 or http://serverip:8080 in your browser. Now you should see the Tomcat welcome page.

PS: As noticed by Robert Douglass, be careful when opening ports in public servers. Apache Solr Does NOT provide security by default so anyone is able to post data towards your system. Best thing to do is to add another rule where you only accept 8080 connections from a certain IP address.

Step 3. Downloading Solr

Download apachesolr to your home directory for temporary reasons.  Get it from one of the mirrors at http://www.apache.org/dyn/closer.cgi/lucene/solr/.  To use the recommended mirror:

cd ~/<br />
        wget &lt;a href=&quot;http://www.motorlogy.com/apachemirror/lucene/solr/1.4.0/apache-solr-1.4.0.tgz<br />
        tar&quot; title=&quot;http://www.motorlogy.com/apachemirror/lucene/solr/1.4.0/apache-solr-1.4.0.tgz<br />
        tar&quot;&gt;http://www.motorlogy.com/apachemirror/lucene/solr/1.4.0/apache-solr-1.4....&lt;/a&gt; -zxvf apache-solr-1.4.0.tgz

 

Step 4. Linking Tomcat6 with Apache Solr application

This should give you an idea on where your distribution installed tomcat6.
Attention: If your path is different, do not forget to also adjust this in the next steps.  whereis tomcat6 should show you tomcat6:

/etc/tomcat6 /usr/share/tomcat6

Copy the solr.war file to the webapps directory:

sudo cp apache-solr-1.4.0/dist/apache-solr-1.4.0.war /usr/share/tomcat6/webapps/solr.war

Copy the example solr application to a new directory called solr. We will change this example solr application later on to be viable for Drupal 6:

sudo cp -R apache-solr-1.4.0/example/solr/ /usr/share/tomcat6/solr/

Create our config file

sudo nano /etc/tomcat6/Catalina/localhost/solr.xml

And fill it with the following configuration :

&lt;Context docBase=&quot;/usr/share/tomcat6/webapps/solr.war&quot; debug=&quot;0&quot; privileged=&quot;true&quot; allowLinking=&quot;true&quot; crossContext=&quot;true&quot;&gt;<br />
        &lt;Environment name=&quot;solr/home&quot; type=&quot;java.lang.String&quot; value=&quot;/usr/share/tomcat6/solr&quot; override=&quot;true&quot; /&gt;<br />
        &lt;/Context&gt;

Step 5. Managing Tomcat6 application

We want to be able to see how and/or if our Solr application is running, and we can do this by using the manager application. By default you don't have access to this application so we have to modify the permissions:

sudo nano /etc/tomcat6/tomcat-users.xml

And modify it so it more or less reflects the same information as shown here.

&lt;tomcat-users&gt;<br />
        &lt;role rolename=&quot;admin&quot;/&gt;<br />
        &lt;role rolename=&quot;manager&quot;/&gt;<br />
        &lt;user username=&quot;nick&quot; password=&quot;ateneatech&quot; roles=&quot;admin,manager&quot;/&gt;<br />
        &lt;/tomcat-users&gt;

Drop Tomcat security so Solr can access /usr/share/tomcat6/solr:

sudo nano /etc/default/tomcat6

And modify it so our security is disabled. Be careful if you are running on a server which you do not control 100%!

TOMCAT6_SECURITY=no

We'll restart Tomcat after we got Solr multi-core installed.
 

 

Step 6. Linking Drupal 6 with a running Apache Solr

 

I assume you have Drush installed, so we continue with downloading the apachesolr module. Execute this command in the designated website:

drush dl apachesolr

You will also need to get the Solr PHP Client library (available at http://code.google.com/p/solr-php-client/downloads/list) and unpack it so it is under the apachesolr module directory as SolrPhpCLient (i.e. sites/all/modules/apachesolr/SolrPHPClient)

Let's copy our schema files that will customize our Apache Solr instance so it fits the "Drupal" bill.

sudo cp ~/apachesolr/schema.xml /usr/share/tomcat6/solr/conf/schema.xml<br />
        sudo cp ~/apachesolr/solrconfig.xml /usr/share/tomcat6/solr/conf/solrconfig.xml

 

Step 7. Set up up the multi-core functionality

First, we copy the solr.xml that is configured for mutli-core to the /solr directory:

sudo cp ~/apache-solr-1.4.0/example/multicore/solr.xml /usr/share/tomcat6/solr/solr.xml

Now create a directory within the /usr/share/tomcat6/solr directory for each site you want to use Solr with.  For example, if I had two sites "site1.com" and "site2.com" and I wanted to use Solr with them both, I might create the following directories:

/usr/share/tomcat6/solr/site1<br />
        /usr/share/tomcat6/solr/site2

Copy the /usr/share/tomcat6/solr/conf directory into each directory you just created. When you're done, each site's directory should have a copy of /conf in them:

sudo cp /usr/share/tomcat6/solr/conf /usr/share/tomcat6/solr/site1/conf<br />
        sudo cp /usr/share/tomcat6/solr/conf /usr/share/tomcat6/solr/site2/conf

Note: DO NOT delete the /usr/share/tomcat6/solr/conf directory.  Even though there is a copy for each site, it is still needed by the Solr core.  For stock Solr, this directory can be deleted, but it is needed when running it in Drupal.

Now alter the solr.xml file you copied over to list both sites.  Your solr.xml should look like this:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;<br />
        &lt;solr persistent=&quot;false&quot;&gt;<br />
        &lt;cores adminPath=&quot;/admin/cores&quot;&gt;<br />
        &nbsp; &lt;core name=&quot;site1&quot; instanceDir=&quot;site1&quot; /&gt;<br />
        &nbsp; &lt;core name=&quot;site2&quot; instanceDir=&quot;site2&quot; /&gt;<br />
        &lt;/cores&gt;<br />
        &lt;/solr&gt;

OK, let's restart our Tomcat service:

sudo /etc/init.d/tomcat6 restart

Surf to http://localhost:8080/manager/html and log in with your username and password from above to check if the Solr instance is started. If not, start and it and check wether or not you receive an error code.  If your application is started, surf to http://localhost:8080/solr/admin and you should see a nice screen!
 

Step 8. Configure the Drupal modules

Enable the "Apache Solr framework" and "Apache Solr search" modules. Also, enable the core Drupal Search module if you haven't already. Do this for both sites.

Navigate to the Solr Settings page for each site (admin/settings/apachesolr/settings):

- Solr host name: localhost<br />
        - Solr port: 8080<br />
        - Solr path for site1.com: /solr/site1<br />
        - Solr path for site2.com: /solr/site2

Under "Advanced Configuration", enabled the "Make Apache Solr Search the default" option if you want to replace the default search with Solr (and I assume you do).

Click "Save Configuration."  The first time around it'll probably tell you it can't reach the server, but if you refresh the page you'll be good to go.

Now, whenever you add a new site, you will just need to do the following steps to enable Solr search:

    Copy /usr/share/tomcat6/solr/conf to /usr/share/tomcat6/solr/mysite
    Add the site to /usr/share/tomcat6/solr/solr.xml (you will need to reload Solr to see the new core)
    Configure Solr settings in Drupal at admin/settings/apachesolr

And that's it.  Happy searching!

Share This

One of the most commonly used features in Drupal is path aliases; that is, the ability to define a custom, clean URL that accurately represents the content contained in that page.  A basic example is creating a mysite.com/about-us URL for an About Us page.  This can also be done in Apache Solr with search result pages.  It's a bit more work than setting up an alias with the Pathauto module, but the end result is a page of customized search results listed at a custom URL.  In this post, I'm going to take the URL of mysite.com/search/apachesolr_search/?filters=type%3Avisualization and create an alias of mysite.com/visualizations.

Before continuing reading, you need to familiarize yourself with this post from James McKinney on creating custom search paths with keywords.  However, what I'm going to show is different, in that instead of using keys (i.e. search terms entered in the search box), I'm going to apply a custom filter, or facet in Solr-speak.

To start, we first define our custom paths in hook_menu():

/**
* Implementation of hook_menu().
*/
function mymodule_solr_menu() {
  $items = array();
  $items['partners'] = array(
    'page callback' => 'mymodule_solr_custom_search',
    'access arguments' => array('search content'),
    'type' => MENU_CALLBACK,
  );
  $items['visualizations'] = array(
    'page callback' => 'mymodule_solr_custom_search',
    'access arguments' => array('search content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

These menu items are defined in mymodule_solr_menu(), which is part of a custom module mymodule_solr.module.  It defines two URLs, mysite.com/visualizations and mysite.com/partners, that both use mymodule_solr_custom_search() as a page callback.  mymodule_solr_custom_search() is a modified copy of apachesolr_search_view() from apachesolr_search.module.

/**
* Create a custom search path.  This is a slightly modified copy of
* apachesolr_search_view from apachesolr_search.module
*/
function mymodule_solr_custom_search($type = 'apachesolr_search') {
  // Search form submits with POST but redirects to GET.
  $results = '';
  if (!isset($_POST['form_id'])) {
    if (empty($type)) {
      // Note: search/X can not be a default tab because it would take on the
      // path of its parent (search). It would prevent remembering keywords when
      // switching tabs. This is why we drupal_goto to it from the parent instead.
      drupal_goto('search/apachesolr_search');
    }
    $keys = trim(mymodule_solr_get_keys());
    $filters = '';
    if ($type == 'apachesolr_search' && isset($_GET['filters'])) {
      $filters = trim($_GET['filters']);
    }
    // Collect the search results.
    //  In this custom function we are searching regardless of whether or not there are $keys
    // which there won't be whenever this function is used
    $results = search_data($keys, 'mymodule_solr');

    if ($results) {
      $results = theme('box', t('Search results'), $results);
    }
    else {
      $results = theme('box', t('Your search yielded no results'), variable_get('apachesolr_search_noresults', apachesolr_search_noresults()));
    }

  }
  // Construct the search form.
  return drupal_get_form('search_form', NULL, $keys, $type) . $results;
}

If you compare this module to apachesolr_search_view(), you'll see there are a few differences.

    Replaced the call to search_get_keys() with a call to mymodule_solr_get_keys().  This is a custom version of search_get_keys() that gets the value from the URL (covered in detail below). 
    Remove the conditions that only call apachesolr_search_execute() if there are values for $keys or $filters, since we don't want to use keywords for this search and we can't apply filters at this point in the process (they are applied later in hook_apachesolr_prepare_query()).
    Replaced the second parameter of $type in the call to search_data() with 'mymodule_solr'.  This will call our custom function mymodule_solr_search() (detailed below), which is an implementation of hook_search().

The next function is mymodule_solr_get_keys(). 

function mymodule_solr_get_keys() {
  static $return;
  if (!isset($return)) {
    $parts = explode('/', $_GET['q']);
    if (count($parts) == 1) {
      $return = array_pop($parts);
    }
    else {
      $return = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
    }
  }
  return $return;
}

The purpose of search_get_keys() is to get the search terms from the URL, which is normally assumed to be /search/apachesolr_search/$keys.  These terms are then passed to solr via search_data() and eventually apachesolr_search_execute().  However, in this case, we are hijacking this function to get the URL, but instead of using the value as a key, we will be using it to apply a filter.  Since this will only get called for specific paths which only have one value in addition to the base path, we get that value and return it to mymodule_solr_search_view().

Our third custom function is mymodule_solr_search().

/**
* Implementation of hook_search().
* Copy of apachesolr_search_search with slight modification
*/
function seedge_solr_search($op = 'search', $keys = NULL) {
  // We're using the $keys variable to store the base path that is passed to apachesolr_search_execute
  // so we need to blank it out since it's not needed as $keys any more
  $custom_path = $keys;
  $keys = '';

  switch ($op) {
    case 'name':
      return t('Search');

    case 'reset':
      apachesolr_clear_last_index('apachesolr_search');
      return;

    case 'status':
      return apachesolr_index_status('apachesolr_search');

    case 'search':
      $filters = isset($_GET['filters']) ? $_GET['filters'] : '';
      $solrsort = isset($_GET['solrsort']) ? $_GET['solrsort'] : '';
      $page = isset($_GET['page']) ? $_GET['page'] : 0;
      try {
        $results = apachesolr_search_execute($keys, $filters, $solrsort, $custom_path, $page);
        return $results;
      }
      catch (Exception $e) {
        watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
        apachesolr_failure(t('Solr search'), $keys);
      }
      break;
  } // switch
}

As I mentioned above, I'm using the $keys variable to pass my filter value from mymodule_solr_search_view() onward, and here is where we take it out of $keys.  Since the $keys variable is actually passed on to apachesolr_search_execute(), we store the value in $custom_path and set $keys to an empty string.  Next, we alter the call to apachesolr_search_execute() from

$results = apachesolr_search_execute($keys, $filters, $solrsort, 'search/' . arg(1), $page);

to

$results = apachesolr_search_execute($keys, $filters, $solrsort, $custom_path, $page);

The final function is an implementation of hook_apachesolr_prepare_query():

/**
* Implementation of hook_apachesolr_prepare_query
*/
function mymodule_solr_apachesolr_prepare_query(&$query, &$params) {
  // Get the $base_path value, since that will tell us what type of content type
  // the filter needs to use
  $base_path = trim($query->get_path(''), "/"); 
  // Add filter for data_sets
  switch ($base_path) {
    case 'visualizations':
      $type = 'visualization';
      break;
    case 'partners':
      $type = 'knowledge_partners';
      break;   
  } 
  if ($type) {
    $query->add_filter('type', $type);
  } 
}

The value stored in $query->base_path is the $custom_path value that was passed to apachesolr_search_execute() in mymodule_solr_search_search() above.  We pass an empty string to get_path so that  only the base path value is returned (see the get_path() function in Solr_Base_Query.php to see why).  Once we have that value, we use the $query->add_filter() method to the $query object.  The first parameter is the type of filter ('type' is the node type), and the second parameter is the value of the filter.

One point that is worth making is the difference between hook_apachesolr_prepare_query() and hook_apachesolr_modify_query().  As I mentioned in my previous post, which one you use is determined by whether or not you want the user to see the change.  Anything modified in prepare_query will be visible to your users, and anything modified in modify_query will not be visible to your users.  Here is the code from apachesolr.module that calls the two hooks to help illustrate why:

// Allow modules to alter the query prior to statically caching it.
  // This can e.g. be used to add available sorts.
  foreach (module_implements('apachesolr_prepare_query') as $module) {
    $function_name = $module . '_apachesolr_prepare_query';
    $function_name($query, $params, $caller);
  }

  // Cache the built query. Since all the built queries go through
  // this process, all the hook_invocations will happen later
  $current_query = apachesolr_current_query($query);

  // This hook allows modules to modify the query and params objects.
  apachesolr_modify_query($query, $params, $caller);
  $params['start'] = $page * $params['rows'];

The only line between the calls to the two hooks is the caching of the query.

This is illustrated in this case by the display of the Apache Solr Search: Current search block; when the code to add the filter with $query->add_filter() is placed in modify_query, the block is not displayed, but when it is placed in prepare_query, it is displayed on the search results page (assuming you have it enabled in your Blocks admin, of course).  I won't go into detail on all the ways I tried to get that block to display, including directly modifying $_GET['q'] before I finally remembered that distinction...

And that's it.  Now, when you navigate to mysite.com/visualizations, you get a page of Solr results with only the content type filter of the visualizations content type applied.  It's a bit of a hack in that I'm using the functionality for passing keywords to pass filter values, but it shows the customization possibilities that are possible with Solr.

Share This

As I mentioned in my previous post on adding a custom sort to Solr, this post is about something that is even simpler; adding a new field to the search results.  In our particular use case, we wanted to add a CCK image field to the results.  To add a slight twist, we needed to add a different image depending on the content type: the user picture for a profile node (created with content_profile module) or a CCK imagefield named field_main_image from the other content types.

To do this, we will use the two hooks used in the last example: hook_apachesolr_update_index() and hook_apachesolr_modify_query().  To start, we need to add the data to the Solr index.

/**
* Implementation of hook_apachesolr_update_index()
*/
function mymodule_apachesolr_update_index(&$document, $node) {
  // Index field_main_image as a separate field
  if ($node->type == 'profile') {
    $user = user_load(array('uid' => $node->uid));
    $document->setMultiValue('sm_field_main_image', $user->picture);
  }
  elseif (count($node->field_main_image)) {
    foreach ($node->field_main_image AS $image) {
      $document->setMultiValue('sm_field_main_image', $image['filepath']);
    }
  }
}

All we do is add the data to the index by adding it to the $doument object, which is passed by reference.  We are using the setMultiValue method since the imagefield can have multiple values, but if we were just adding one field, we would just use the addField method.  And, actually, according to the comments in the setMultiValue method, it is deprecated in favor of using addField, but this is working fine right now.  The field name is simply the 'sm_' dynamic field name pattern with field_main_image appended, since the field contains a string of the path to the image, and the sm_ field type represents a medium string (as defined in schema.xml).

Now that the data has been added to the index, we also need to add it to the query so it can be returned in the search results.  This is done amazingly easily:

function mymodule_apachesolr_modify_query(&$query, &$params, $caller) {
  $params['fl'] .= ',sm_field_main_image';
}

Without going into too much detail on the format of $params, all you're doing is some basic PHP string concatenation and appending your newly indexed field to the fields to return array (['fl'])of the $params object.  For more information on $params, see the DCSF session video starting at 37:30.

And that's all there is to it; two very small hook implementations. Pretty easy, and pretty powerful.  Now that we have that down, we can do even more complex stuff.  My next post on Solr will be a slightly more involved example of creating a custom search path.

Share This

I attended Drupalcon for the first time this year in San Francisco, and of all the sessions I attended, one that really stood out for me was Apache Solr Search Mastery, put on by Robert Douglass and Peter Wolanin of Acquia and James McKinney of Evolving Web.  It was like the first time I read "Pro Drupal Development", in that it really opened my eyes as to what is possible with the Solr module.  On top of that, I was almost immediately able to take what I learned back to a project I was working on and start using it.

One of the first use cases I had was the need to add a custom sort to the search results.  Out of the box, the module has the following five sorts available:

  •     Relevancy
  •     Title
  •     Type
  •     Author
  •     Date

However, the client also wanted to add a search by the number of times a node had been viewed, as provided by the core Statistics module.  In order to make this data available in Solr as a sortable field, we have to follow a two step process:

  1.     Add the field to the Solr index
  2.     Tell Solr that the field is sortable

Adding the field to the index

The apachesolr module has multiple available hooks that can be used to alter the search data.  In this case, the one that we want is hook_apachesolr_update_index.  This hook is run for each node, and is passed the $document object by reference.  $document is the XML document that is passed to Solr for indexing.  The source for our data is the totalcount field in the node_counter table.

function mymodule_apachesolr_update_index(&amp;$document, $node) {<br />
        &nbsp; // Get count from node_counter table<br />
        &nbsp; $count = db_result(db_query(&quot;SELECT totalcount FROM {node_counter} WHERE nid = %d&quot;, $node-&gt;nid));<br />
        <br />
        &nbsp; // Add field to index<br />
        &nbsp; if ($count !== FALSE) {<br />
        &nbsp;&nbsp;&nbsp; $document-&gt;tis_hit_count = (int) $count;<br />
        &nbsp; }<br />
        }}

So all we do is run a query to get our count value, and add it to the Solr index in the tis_hit_count field, but only if the query returns a value.  If you try to add an empty value, such as the case where there is no record in node_counter for a node yet, Solr will throw an error.

One important thing to notice is the field name.  The 'tis_' at the beginning of the name tells Solr what kind of data is stored in the field.  If you look in the schema.xml file that comes with the module (and that you use to replace the default Solr file when installing the module), there is a section that lists all of the dynamic field naming patterns.

&lt;!-- Dynamic field definitions.&nbsp; If a field name is not found, dynamicFields<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; will be used if the name matches any of the patterns.<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RESTRICTION: the glob-like pattern in the name attribute must have<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a &quot;*&quot; only at the start or the end.<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EXAMPLE:&nbsp; name=&quot;*_i&quot; will match any field ending in _i (like myid_i, z_i)<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Longer patterns will be matched first.&nbsp; if equal size patterns<br />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; both match, the first appearing in the schema will be used.&nbsp; --&gt;<br />
        <br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;is_*&quot;&nbsp; type=&quot;integer&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;im_*&quot;&nbsp; type=&quot;integer&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;sis_*&quot; type=&quot;sint&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;sim_*&quot; type=&quot;sint&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;sm_*&quot;&nbsp; type=&quot;string&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tm_*&quot;&nbsp; type=&quot;text&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot; termVectors=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;ss_*&quot;&nbsp; type=&quot;string&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;ts_*&quot;&nbsp; type=&quot;text&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot; termVectors=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tsen2k_*&quot; type=&quot;edge_n2_kw_text&quot; indexed=&quot;true&quot; stored=&quot;true&quot; multiValued=&quot;false&quot; omitNorms=&quot;true&quot; omitTermFreqAndPositions=&quot;true&quot; /&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;ds_*&quot; type=&quot;date&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;dm_*&quot; type=&quot;date&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tds_*&quot; type=&quot;tdate&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tdm_*&quot; type=&quot;tdate&quot;&nbsp;&nbsp;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;bm_*&quot;&nbsp; type=&quot;boolean&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;bs_*&quot;&nbsp; type=&quot;boolean&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;fs_*&quot;&nbsp; type=&quot;sfloat&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;fm_*&quot;&nbsp; type=&quot;sfloat&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;ps_*&quot;&nbsp; type=&quot;sdouble&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;pm_*&quot;&nbsp; type=&quot;sdouble&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        <br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tis_*&quot;&nbsp; type=&quot;tint&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tim_*&quot;&nbsp; type=&quot;tint&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tls_*&quot;&nbsp; type=&quot;tlong&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tlm_*&quot;&nbsp; type=&quot;tlong&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tfs_*&quot;&nbsp; type=&quot;tfloat&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tfm_*&quot;&nbsp; type=&quot;tfloat&quot;&nbsp; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tps_*&quot;&nbsp; type=&quot;tdouble&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;tpm_*&quot;&nbsp; type=&quot;tdouble&quot; indexed=&quot;true&quot;&nbsp; stored=&quot;true&quot; multiValued=&quot;true&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;!-- Sortable version of the dynamic string field --&gt;<br />
        &nbsp;&nbsp; &lt;dynamicField name=&quot;sort_ss_*&quot; type=&quot;sortString&quot; indexed=&quot;true&quot; stored=&quot;false&quot;/&gt;<br />
        &nbsp;&nbsp; &lt;copyField source=&quot;ss_*&quot; dest=&quot;sort_ss_*&quot;/&gt;

 

In this case, the 'tint' field type is most appropriate for this data.  For more information on naming fields you can read the schema.xml document, or watch the video from the Drupalcon session (Peter talks about dynamic fields starting at 15:50 into the video).

Once this has been saved, you will need to re-index the content at admin/settings/apachesolr/index.  You can then go to admin/reports/apachesolr and verify that your field has been indexed.
Telling Solr that the field is sortable

Now that the data has been added to the index, we need to tell Solr that the field is sortable.  To do this, we use another hook, hook_apachesolr_prepare_query().  This hook is run after the query has been generated by Solr, and it allows you to make any modifications before it is run.

 

function mymodule_apachesolr_prepare_query(&amp;$query, &amp;$params) {<br />
        &nbsp; $query-&gt;set_available_sort(&#39;tis_hit_count&#39;, array(<br />
        &nbsp;&nbsp;&nbsp; &#39;title&#39; =&gt; t(&#39;Number of Views&#39;),<br />
        &nbsp;&nbsp;&nbsp; &#39;default&#39; =&gt; &#39;asc&#39;,<br />
        &nbsp; ));<br />
        <br />
        }

As you can see in the code above, the $query object is passed by reference, and we use the set_available_sort method to add this as a sort field that will be displayed in the Apache Solr Sorting: Core block.  The first parameter is the field name that was defined in hook_apachesolr_update_index(), and the second is an array that contains the string that will be displayed in the Sorting block, and the type of sort (ascending or descending).  In addition, if you do not want to use any of the default sorts, they can be removed at the same time with the remove_available_sort method. passing the field name:

$query-&gt;remove_available_sort(&#39;solr_field_name&#39;);

And voila!  You should now have a Number of Views link in your Sort block.

One additional thing to note is that there are actually two different hooks for modifying the $query object: hook_apachesolr_prepare_query and hook_apachesolr_modify_query.  The one you want to use is determined by whether or not you want the user to see the change.  Anything modified in prepare_query will be visible to your users, and anything modified in modify_query will not be visible to your users (a more detailed explanation is given by James McKinney in the DCSF session video from 37:20-39:15).  In this case, since we want the change to be visible to our users, we use hook_apachesolr_prepare_query.

There are many more customizations that can be done in Solr with the available hooks.  In subsequent posts, I will show how to add a field so that it is displayed with the search results, and a more complicated use case of creating a custom search path that automatically applies a filter to the results.

Share This

Drupal Staffing and Consulting has officially changed its name to Drupal Connect.

Our new name more closely aligns with who we are today as an organization. After initially starting out as a staffing company specializing in finding Drupal talent, we have now evolved into a full-service firm that provides Drupal development, consulting and training services to more than 100 clients throughout North America, some of whom are Fortune 100 companies including GE, NBC and A&E Networks. In the past year, Drupal Connect has been engaged in some of the largest and most complex Drupal projects on the Web. Our philosophy is simple, hire the best people and provide our clients with the best service possible.

Since our inception in February of 2009, we have engaged in over 50 web development projects and employed over 200 Drupal developers across North America. In addition, we have also sponsored close to a dozen Drupal camps all across the US including most recently becoming gold sponsors at the Drupal design camp in Boston, MA. For the remainder of 2010 our prospects remain strong and our outlook extremely promising. We are extremely fortunate to have a team of brilliant people who are hard working and passionate for their love of Drupal.

Share This

Pages