Last updated by grantmc 3 years ago


This FAQ is a work in progress!

Please add stuff that you think others may find helpful.

Negative numbers are indexed as positive

This is probably because the number is being "analyzed" during the index process.

This process is usually applied to searchable text to normalize it and remove what are normally useless words and characters (punctuation for example).

If you want to store the value of any searchable property exactly as it appears map that field with @index: "not_analyzed"@.

class Idea {
    static searchable = {
        votes index: "not_analyzed"
    int votes
    // …

Number Range searches don't return the expected results

Searching for objects with values for a numeric property in a certain range may return unexpected results.

Say you have the following domain class:

class Bug {
    static searchable = {
        votes index: "not_analyzed"
    int votes = 0
    // …

And you want to search for Bugs with between 0 and 20 votes. With the Searchable plugin you could do it this way

def results ="votes:[0 TO 20]")

But you might get back bugs with votes with 100 votes! What gives?

Data in the search index is not typed like a database, everything is text. So comparisons are done lexicographically, rather than numerically as you would expect in this case.

The solution is to apply some formatting that pads the number with zeros. The "format" mapping option defines how the number is converted to searchable text in the index:

class Bug {
    static searchable = {
        votes index: "not_analyzed", format: "000000000"
    int votes = 0
    // …

With this mapping the search above works as you would expect.

Note: search indexes are like databases in some respects and you need to think about the storage of every piece of data in there. Giving something too much room may result in a larger than necessary search index (on disk). Compare the search index mapping to the database column to fine the right amount of padding for number formats.

Startup is slow

The plugin performs a synchronous bulk-index of all the searchable domain class instances in your app by default.

You have a few options:

Bulk index at startup

You can change the default bulk-index-on-startup setting with configuration: you can disable it or fork a new thread.

Even if you disable it, you can always call searchableService.index() to perform a complete index at any time

You can also perform a bulk-index in a separate thread easily like:

Thread.start {
    println "forked bulk index thread"
    println "bulk index thread finished"

Which is a good thing since whilst performing a bulk-index Compass does not actually destroy the current index until the bulk-index is finished, so if you have a previous index you can do searches even while the index is being refreshed.

Disable mirroring during bootstrap

If mirroring is enabled (default is on) and you are creating domain classes in your BootStrap#init you can temporarily disable mirroring while the bootstrap process runs then enable it and perform a complete index:

class BootStrap {
    def searchableService

def init = { servletContext -> searchableService.stopMirroring()

for (i in 0..<10000) { def thingie = new Thingie(description: "this right here is a thingie and it's number ${i}") assert thingie.validate(), thingie.errors }

searchableService.startMirroring() searchableService.indexAll() }

def destroy = { } }

Does Searchable work with multiple datasources?

It does, but you will need to add a few things to your application to get it working.

You will likely get the following error if you have multiple datasources, or a datasource with a non-default name (like dataSource_users ):

GrailsContextLoader Error initializing the application: No entities listed to be indexed, have you defined your entities correctly?
java.lang.IllegalArgumentException: No entities listed to be indexed, have you defined your entities correctly?

To correct this issue, add the following to resources.groovy :

import org.compass.gps.device.hibernate.HibernateGpsDevice
import grails.plugin.searchable.internal.compass.config.SessionFactoryLookup

beans = { compassGpsDevice(HibernateGpsDevice) { bean -> bean.destroyMethod = "stop" name = "hibernate" sessionFactory = { SessionFactoryLookup sfl -> sessionFactory = ref('sessionFactory_datasourceName') } fetchCount = 5000 } }

To add other dataSources with indexable classes, add the following:

anotherUniquecompassGpsDevice(HibernateGpsDevice) { bean ->
    bean.destroyMethod = "stop"
    name = "unqiueHibernateName"
    sessionFactory = { SessionFactoryLookup sfl ->
        sessionFactory = ref('sessionFactory_uniquedatasource') 
    fetchCount = 5000

And finally add...

import org.compass.gps.impl.SingleCompassGps

compassGps(SingleCompassGps) { compass = ref('compass') gpsDevices = [compassGpsDevice, anotherUniqueCompassGpsDevice] }

(Thanks to for this fix. Read the original blog post here.)