Skip to content

Commit

Permalink
Extract query building and result processing from SolrService
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Dec 1, 2014
1 parent 56e223f commit 2585da1
Show file tree
Hide file tree
Showing 36 changed files with 291 additions and 220 deletions.
2 changes: 2 additions & 0 deletions lib/active_fedora.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module ActiveFedora #:nodoc:
autoload :Persistence
autoload :QualifiedDublinCoreDatastream
autoload :Querying
autoload :QueryResultBuilder
autoload :RDF
autoload_under 'rdf' do
autoload :RDFDatastream
Expand All @@ -91,6 +92,7 @@ module ActiveFedora #:nodoc:
autoload :Serialization
autoload :SimpleDatastream
autoload :SolrInstanceLoader
autoload :SolrQueryBuilder
autoload :SolrService
autoload :SparqlInsert
autoload :Predicates
Expand Down
4 changes: 2 additions & 2 deletions lib/active_fedora/associations/association_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def add_constraints(scope)
chain.each_with_index do |reflection, i|
if reflection.source_macro == :belongs_to
# Create a partial solr query using the ids. We may add additional filters such as class_name later
scope = scope.where( ActiveFedora::SolrService.construct_query_for_ids([owner[reflection.foreign_key]]))
scope = scope.where( ActiveFedora::SolrQueryBuilder.construct_query_for_ids([owner[reflection.foreign_key]]))
elsif reflection.source_macro == :has_and_belongs_to_many
else
scope = scope.where( ActiveFedora::SolrService.construct_query_for_rel(association.send(:find_reflection) => owner.id))
scope = scope.where( ActiveFedora::SolrQueryBuilder.construct_query_for_rel(association.send(:find_reflection) => owner.id))
end

is_first_chain = i == 0
Expand Down
4 changes: 2 additions & 2 deletions lib/active_fedora/associations/belongs_to_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def construct_query(ids)
candidate_classes = klass.descendants.select {|d| d.name }
candidate_classes += [klass] unless klass == ActiveFedora::Base
model_pairs = candidate_classes.inject([]) { |arr, klass| arr << [:has_model, klass.to_class_uri]; arr }
'(' + ActiveFedora::SolrService.construct_query_for_ids(ids) + ') AND (' +
ActiveFedora::SolrService.construct_query_for_rel(model_pairs, 'OR') + ')'
'(' + ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids) + ') AND (' +
ActiveFedora::SolrQueryBuilder.construct_query_for_rel(model_pairs, 'OR') + ')'
end

def foreign_key_present?
Expand Down
4 changes: 2 additions & 2 deletions lib/active_fedora/associations/collection_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def load_target
def find_target
# TODO: don't reify, just store the solr results and lazily reify.
# For now, we set a hard limit of 1000 results.
records = ActiveFedora::SolrService.reify_solr_results(load_from_solr(rows: 1000))
records = ActiveFedora::QueryResultBuilder.reify_solr_results(load_from_solr(rows: 1000))
records.each { |record| set_inverse_instance(record) }
records
end
Expand Down Expand Up @@ -333,7 +333,7 @@ def construct_query
#TODO use primary_key instead of id
clauses = { find_reflection => @owner.id }
clauses[:has_model] = @reflection.class_name.constantize.to_class_uri if @reflection.class_name && @reflection.class_name != 'ActiveFedora::Base'
@counter_query = @finder_query = ActiveFedora::SolrService.construct_query_for_rel(clauses)
@counter_query = @finder_query = ActiveFedora::SolrQueryBuilder.construct_query_for_rel(clauses)
end


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ def find_target
return [] if ids.blank?
solr_result = []
0.step(ids.size,page_size) do |startIdx|
query = ActiveFedora::SolrService.construct_query_for_ids(ids.slice(startIdx,page_size))
query = ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids.slice(startIdx,page_size))
solr_result += ActiveFedora::SolrService.query(query, rows: page_size)
end
return ActiveFedora::SolrService.reify_solr_results(solr_result)
return ActiveFedora::QueryResultBuilder.reify_solr_results(solr_result)
end

# In a HABTM, just look in the RDF, no need to run a count query from solr.
Expand Down
4 changes: 2 additions & 2 deletions lib/active_fedora/associations/rdf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ def rdf_query
def filter_by_class(candidate_uris)
return [] if candidate_uris.empty?
ids = candidate_uris.map {|uri| ActiveFedora::Base.uri_to_id(uri) }
results = ActiveFedora::SolrService.query(ActiveFedora::SolrService.construct_query_for_ids(ids), rows: 10000)
results = ActiveFedora::SolrService.query(ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids), rows: 10000)

docs = results.select do |result|
ActiveFedora::SolrService.classes_from_solr_document(result).any? { |klass|
ActiveFedora::QueryResultBuilder.classes_from_solr_document(result).any? { |klass|
class_ancestors(klass).include? reflection.klass
}
end
Expand Down
6 changes: 3 additions & 3 deletions lib/active_fedora/indexing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(obj)
end

def self.profile_solr_name
ActiveFedora::SolrService.solr_name("object_profile", :displayable)
ActiveFedora::SolrQueryBuilder.solr_name("object_profile", :displayable)
end


Expand All @@ -26,7 +26,7 @@ def generate_solr_document
Solrizer.set_field(solr_doc, 'system_create', c_time, :stored_sortable)
Solrizer.set_field(solr_doc, 'system_modified', m_time, :stored_sortable)
Solrizer.set_field(solr_doc, 'active_fedora_model', object.class.inspect, :stored_sortable)
solr_doc.merge!(SolrService::HAS_MODEL_SOLR_FIELD => object.has_model)
solr_doc.merge!(QueryResultBuilder::HAS_MODEL_SOLR_FIELD => object.has_model)
solr_doc.merge!(SOLR_DOCUMENT_ID.to_sym => object.id)
solr_doc.merge!(self.class.profile_solr_name => object.to_json)
object.attached_files.each do |name, file|
Expand Down Expand Up @@ -63,7 +63,7 @@ def solrize_relationships(solr_doc = Hash.new)
end

def solr_name(*args)
ActiveFedora::SolrService.solr_name(*args)
ActiveFedora::SolrQueryBuilder.solr_name(*args)
end

end
Expand Down
4 changes: 0 additions & 4 deletions lib/active_fedora/om_datastream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ def to_solr(solr_doc = {}, opts = {})
solr_doc.merge super({}).each_with_object({}) { |(key, value), new| new[[prefix,key].join] = value }
end

def generate_solr_symbol(base, data_type)
ActiveFedora::SolrService.solr_name([prefix,base].join, type: data_type)
end

# ** Experimental **
#@return [Boolean] true if either the key for name exists in solr or if its string value exists
#@param [String] name Name of key to look for
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/qualified_dublin_core_datastream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def to_solr(solr_doc = Hash.new, opts = {}) # :nodoc:
@fields.each do |field_key, field_info|
things = send(field_key)
if things
field_symbol = ActiveFedora::SolrService.solr_name(field_key, type: field_info[:type])
field_symbol = ActiveFedora::SolrQueryBuilder.solr_name(field_key, type: field_info[:type])
things.val.each do |val|
::Solrizer::Extractor.insert_solr_field_value(solr_doc, field_symbol, val )
end
Expand Down
58 changes: 58 additions & 0 deletions lib/active_fedora/query_result_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module ActiveFedora
module QueryResultBuilder
def self.lazy_reify_solr_results(solr_results, opts = {})
Enumerator.new do |yielder|
solr_results.each do |hit|
yielder.yield(reify_solr_result(hit, opts))
end
end
end

def self.reify_solr_results(solr_results, opts = {})
solr_results.collect {|hit| reify_solr_result(hit, opts)}
end

def self.reify_solr_result(hit, opts = {})
klass = class_from_solr_document(hit)
klass.find(hit[SOLR_DOCUMENT_ID], cast: true)
end

#Returns all possible classes for the solr object
def self.classes_from_solr_document(hit, opts = {})
#Add ActiveFedora::Base as never stored in Solr explicitely.
#classes = [ActiveFedora::Base]
classes = []

hit[HAS_MODEL_SOLR_FIELD].each { |value| classes << Model.from_class_uri(value) }

classes.compact
end

#Returns the best singular class for the solr object
def self.class_from_solr_document(hit, opts = {})
#Set the default starting point to the class specified, if available.
best_model_match = Model.from_class_uri(opts[:class]) unless opts[:class].nil?
Array(hit[HAS_MODEL_SOLR_FIELD]).each do |value|

model_value = Model.from_class_uri(value)

if model_value

# Set as the first model in case opts[:class] was nil
best_model_match ||= model_value

# If there is an inheritance structure, use the most specific case.
if best_model_match > model_value
best_model_match = model_value
end
end
end

ActiveFedora::Base.logger.warn "Could not find a model for #{hit["id"]}, defaulting to ActiveFedora::Base" unless best_model_match if ActiveFedora::Base.logger
best_model_match || ActiveFedora::Base
end

HAS_MODEL_SOLR_FIELD = SolrQueryBuilder.solr_name("has_model", :symbol).freeze

end
end
2 changes: 1 addition & 1 deletion lib/active_fedora/querying.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def self.extended(base)
end

def default_sort_params
[ActiveFedora::SolrService.solr_name(:system_create, :stored_sortable, type: :date)+' asc']
[ActiveFedora::SolrQueryBuilder.solr_name(:system_create, :stored_sortable, type: :date)+' asc']
end

end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/rdf/indexing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def primary_solr_name(field, file_path)
return nil unless config # punt on index names for deep nodes!
if behaviors = config.behaviors
behaviors.each do |behavior|
result = ActiveFedora::SolrService.solr_name(apply_prefix(field, file_path), behavior, type: config.type)
result = ActiveFedora::SolrQueryBuilder.solr_name(apply_prefix(field, file_path), behavior, type: config.type)
return result if Solrizer::DefaultDescriptors.send(behavior).evaluate_suffix(:text).stored?
end
raise RuntimeError "no stored fields were found"
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def predicate
end

def solr_key
ActiveFedora::SolrService.solr_name(predicate.to_s, :symbol)
ActiveFedora::SolrQueryBuilder.solr_name(predicate.to_s, :symbol)
end

def check_validity!
Expand Down
6 changes: 3 additions & 3 deletions lib/active_fedora/relation/finder_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def find_one(id, cast=nil)
if where_values.empty?
load_from_fedora(id, cast)
else
conditions = where_values + [ActiveFedora::SolrService.raw_query(SOLR_DOCUMENT_ID, id)]
conditions = where_values + [ActiveFedora::SolrQueryBuilder.raw_query(SOLR_DOCUMENT_ID, id)]
query = conditions.join(" AND ".freeze)
to_enum(:find_each, query, {}).to_a.first
end
Expand Down Expand Up @@ -259,7 +259,7 @@ def condition_to_clauses(key, value)
# if the key is a property name, turn it into a solr field
if @klass.delegated_attributes.key?(key)
# TODO Check to see if `key' is a possible solr field for this class, if it isn't try :searchable instead
key = ActiveFedora::SolrService.solr_name(key, :stored_searchable, type: :string)
key = ActiveFedora::SolrQueryBuilder.solr_name(key, :stored_searchable, type: :string)
end

if value.empty?
Expand All @@ -278,7 +278,7 @@ def search_model_clause
# The concrete class could could be any subclass of @klass or @klass itself
unless @klass == ActiveFedora::Base
clauses = ([@klass] + @klass.descendants).map do |k|
ActiveFedora::SolrService.construct_query_for_rel(has_model: k.to_s)
ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: k.to_s)
end
clauses.size == 1 ? clauses.first : "(#{clauses.join(" OR ")})"
end
Expand Down
23 changes: 11 additions & 12 deletions lib/active_fedora/simple_datastream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ def initialize(digital_object=nil, dsid=nil, options={})

# This method generates the various accessor and mutator methods on self for the datastream metadata attributes.
# each field will have the 2 magic methods:
# name=(arg)
# name
# name=(arg)
# name
#
#
# 'datatype' is a datatype, currently :string, :integer and :date are supported.
#
# opts is an options hash, which will affect the generation of the xml representation of this datastream.
#
# Currently supported modifiers:
# Currently supported modifiers:
# For +SimpleDatastream+:
# :element_attrs =>{:foo=>:bar} - hash of xml element attributes
# :xml_node => :nodename - The xml node to be used to represent this object (in dcterms namespace)
Expand All @@ -45,8 +45,8 @@ def initialize(digital_object=nil, dsid=nil, options={})
#
#There is quite a good example of this class in use in spec/examples/oral_history.rb
#
#!! Careful: If you declare two fields that correspond to the same xml node without any qualifiers to differentiate them,
#you will end up replicating the values in the underlying datastream, resulting in mysterious dubling, quadrupling, etc.
#!! Careful: If you declare two fields that correspond to the same xml node without any qualifiers to differentiate them,
#you will end up replicating the values in the underlying datastream, resulting in mysterious dubling, quadrupling, etc.
#whenever you edit the field's values.
def field(name, datatype=:string, opts={})
fields ||= {}
Expand All @@ -59,9 +59,8 @@ def field(name, datatype=:string, opts={})
self.class.terminology.add_term(term)
term.generate_xpath_queries!
end

end

def update_indexed_attributes(params={}, opts={})
raise "can't modify frozen #{self.class}" if frozen?
# if the params are just keys, not an array, make then into an array.
Expand All @@ -75,7 +74,7 @@ def update_indexed_attributes(params={}, opts={})
end
super(new_params, opts)
end


def self.xml_template
Nokogiri::XML::Document.parse("<fields/>")
Expand All @@ -85,10 +84,10 @@ def to_solr(solr_doc = Hash.new, opts = {}) # :nodoc:
@fields.each do |field_key, field_info|
next if field_key == :location ## FIXME HYDRA-825
things = send(field_key)
if things
field_symbol = ActiveFedora::SolrService.solr_name(field_key, type: field_info[:type])
things.val.each do |val|
::Solrizer::Extractor.insert_solr_field_value(solr_doc, field_symbol, val.to_s )
if things
field_symbol = ActiveFedora::SolrQueryBuilder.solr_name(field_key, type: field_info[:type])
things.val.each do |val|
::Solrizer::Extractor.insert_solr_field_value(solr_doc, field_symbol, val.to_s )
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/solr_instance_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def validate_solr_doc_and_id!(document)
end

def active_fedora_class
@active_fedora_class ||= ActiveFedora::SolrService.class_from_solr_document(solr_doc)
@active_fedora_class ||= ActiveFedora::QueryResultBuilder.class_from_solr_document(solr_doc)
end

def profile_json
Expand Down
57 changes: 57 additions & 0 deletions lib/active_fedora/solr_query_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module ActiveFedora
module SolrQueryBuilder
# Construct a solr query for a list of ids
# This is used to get a solr response based on the list of ids in an object's RELS-EXT relationhsips
# If the id_array is empty, defaults to a query of "id:NEVER_USE_THIS_ID", which will return an empty solr response
# @param [Array] id_array the ids that you want included in the query
def self.construct_query_for_ids(id_array)
q = id_array.reject { |x| x.blank? }.map { |id| raw_query(SOLR_DOCUMENT_ID, id) }
q.empty? ? "id:NEVER_USE_THIS_ID" : q.join(" OR ".freeze)
end

# Create a raw query clause suitable for sending to solr as an fq element
# @param [String] key
# @param [String] value
def self.raw_query(key, value)
"_query_:\"{!raw f=#{key}}#{value.gsub('"', '\"')}\""
end

def self.solr_name(*args)
Solrizer.default_field_mapper.solr_name(*args)
end

# Create a query with a clause for each key, value
# @param [Hash, Array<Array<String>>] args key is the predicate, value is the target_uri
# @param [String] join_with ('AND') the value we're joining the clauses with
# @example
# construct_query_for_rel [[:has_model, "info:fedora/afmodel:ComplexCollection"], [:has_model, "info:fedora/afmodel:ActiveFedora_Base"]], 'OR'
# # => _query_:"{!raw f=has_model_ssim}info:fedora/afmodel:ComplexCollection" OR _query_:"{!raw f=has_model_ssim}info:fedora/afmodel:ActiveFedora_Base"
#
# construct_query_for_rel [[Book.reflect_on_association(:library), "foo/bar/baz"]]
def self.construct_query_for_rel(field_pairs, join_with = 'AND')
field_pairs = field_pairs.to_a if field_pairs.kind_of? Hash

clauses = pairs_to_clauses(field_pairs.reject { |_, target_uri| target_uri.blank? })
clauses.empty? ? "id:NEVER_USE_THIS_ID" : clauses.join(" #{join_with} ".freeze)
end

private
# Given an list of 2 element lists, transform to a list of solr clauses
def self.pairs_to_clauses(pairs)
pairs.map do |field, target_uri|
raw_query(solr_field(field), target_uri)
end
end

# @param [String, ActiveFedora::Relation] field
# @return [String] the corresponding solr field for the string
def self.solr_field(field)
case field
when ActiveFedora::Reflection::AssociationReflection
field.solr_key
else
solr_name(field, :symbol)
end
end
end
end

0 comments on commit 2585da1

Please sign in to comment.