# encoding: utf-8
# This is the top level module of xapian_db. It allows you to
# configure XapianDB, create / open databases and perform
# searches.
# @author Gernot Kogler
require 'xapian'
require 'json'
do_not_require = %w(update_stopwords railtie base_adapter generic_adapter active_record_adapter datamapper_adapter
beanstalk_writer resque_writer sidekiq_writer utilities install_generator datamapper)
files = Dir.glob("#{File.dirname(__FILE__)}/**/*.rb").reject{|path| do_not_require.include?(File.basename(path, ".rb"))}
# Require these first
require "#{File.dirname(__FILE__)}/xapian_db/utilities"
require "#{File.dirname(__FILE__)}/xapian_db/adapters/base_adapter"
files.each {|file| require file}
# Configure XapianDB if we are in a Rails app
require File.dirname(__FILE__) + '/xapian_db/railtie' if defined?(Rails)
module XapianDb
# Supported languages
LANGUAGE_MAP = {:da => :danish,
:nl => :dutch,
:en => :english,
:fi => :finnish,
:fr => :french,
:de => :german2, # Normalises umlauts and ß
:hu => :hungarian,
:it => :italian,
:nb => :norwegian,
:nn => :norwegian,
:no => :norwegian,
:pt => :portuguese,
:ro => :romanian,
:ru => :russian,
:es => :spanish,
:sv => :swedish,
:tr => :turkish}
# Global configuration for XapianDb. See {XapianDb::Config.setup}
# for available options
def self.setup(&block)
XapianDb::Config.setup(&block)
@writer = XapianDb::Config.writer
end
# Create a database
# @param [Hash] options
# @option options [String] :path A path to the file system. If no path is
# given, creates an in memory database. Overwrites an existing database!
# @return [XapianDb::Database]
def self.create_db(options = {})
if options[:path]
PersistentDatabase.new(:path => options[:path], :create => true)
else
InMemoryDatabase.new
end
end
# Open a database
# @param [Hash] options
# @option options [String] :path A path to the file system. If no path is
# given, creates an in memory database. If a path is given, then database
# must exist.
# @return [XapianDb::Database]
def self.open_db(options = {})
if options[:path]
PersistentDatabase.new(:path => options[:path], :create => false)
else
InMemoryDatabase.new
end
end
# Access the configured database. See {XapianDb::Config.setup}
# for instructions on how to configure a database
# @return [XapianDb::Database]
def self.database
XapianDb::Config.database
end
# Query the configured database.
# See {XapianDb::Database#search} for options
# @return [XapianDb::Resultset]
def self.search(expression, options={})
order = options.delete :order
if order
attr_names = [order].flatten
undefined_attrs = attr_names - XapianDb::DocumentBlueprint.attributes
raise ArgumentError.new "invalid order clause: attributes #{undefined_attrs.inspect} are not defined" unless undefined_attrs.empty?
options[:sort_indices] = attr_names.map {|attr_name| XapianDb::DocumentBlueprint.value_number_for(attr_name) }
end
XapianDb::Config.database.search(expression, options)
end
# Get facets from the configured database.
# See {XapianDb::Database#facets} for options
# @return [Hash] A hash containing the classes and the hits per class
def self.facets(attribute, expression)
XapianDb::Config.database.facets attribute, expression
end
# Update an object in the index
# @param [Object] obj An instance of a class with a blueprint configuration
def self.index(obj, commit=true)
writer = @block_writer || XapianDb::Config.writer
writer.index obj, commit
end
# Remove a document from the index
# @param [String] xapian_id The document id
def self.delete_doc_with(xapian_id, commit=true)
writer = @block_writer || XapianDb::Config.writer
writer.delete_doc_with xapian_id, commit
end
# Update or delete a xapian document belonging to an object depending on the ignore_if logic(if present)
# @param [Object] object An instance of a class with a blueprint configuration
def self.reindex(object, commit=true)
writer = @block_writer || XapianDb::Config.writer
blueprint = XapianDb::DocumentBlueprint.blueprint_for object.class.name
if blueprint.should_index?(object)
writer.index object, commit
else
writer.delete_doc_with object.xapian_id, commit
end
end
# Reindex all objects of a given class
# @param [Class] klass The class to reindex
# @param [Hash] options Options for reindexing
# @option options [Boolean] :verbose (false) Should the reindexing give status informations?
def self.reindex_class(klass, options={})
XapianDb::Config.writer.reindex_class klass, options
end
# Rebuild the xapian index for all configured blueprints
# @param [Hash] options Options for reindexing
# @option options [Boolean] :verbose (false) Should the reindexing give status informations?
# @return [Boolean] Did we reindex anything?
def self.rebuild_xapian_index(options={})
configured_classes = XapianDb::DocumentBlueprint.configured_classes
return false unless configured_classes.size > 0
configured_classes.each do |klass|
if klass.respond_to?(:rebuild_xapian_index)
XapianDb::Config.writer.reindex_class(klass, options)
end
end
true
end
# Execute a block as a transaction
def self.transaction(&block)
writer = XapianDb::IndexWriters::TransactionalWriter.new
execute_block :writer => writer, :error_message => "error in XapianDb transaction block, transaction aborted" do
block.call
writer.commit_using XapianDb::Config.writer
end
end
# Execute a block and do not update the index
def self.auto_indexing_disabled(&block)
execute_block :writer => XapianDb::IndexWriters::NoOpWriter do
block.call
end
end
# execute a block of code with a given writer and handle errors
# @param [Hash] opts Options
# @option opts [Object] :writer An index writer
# @option opts [String] :error_message the error message to log if an error occurs
def self.execute_block(opts, &block)
@block_writer = opts[:writer]
begin
block.call
rescue Exception => ex
if opts[:error_message]
if defined?(Rails)
Rails.logger.error opts[:error_message]
else
puts opts[:error_message]
end
end
raise
ensure
# release the block writer
@block_writer = nil
end
end
end