lib/spiderfw/model/mappers/db_mapper.rb in spiderfw-0.5.9 vs lib/spiderfw/model/mappers/db_mapper.rb in spiderfw-0.5.10

- old
+ new

@@ -134,11 +134,11 @@ return nil unless save[:values].length > 0 condition = Condition.and @model.primary_keys.each do |key| condition[key.name] = map_condition_value(key.type, obj.get(key)) end - prepare_query_condition(condition) + preprocess_condition(condition) save[:condition], save[:joins] = prepare_condition(condition) save[:joins] = prepare_joins(save[:joins]) save[:table] = @schema.table return @storage.sql_update(save) end @@ -146,11 +146,11 @@ # Updates according to a condition, storing the values, which must passed as a Hash. def bulk_update(values, condition) db_values = {} joins = [] integrated = {} - condition = prepare_query_condition(condition) + condition = preprocess_condition(condition) values.each do |key, val| element = @model.elements[key] if (element.integrated?) integrated[element.integrated_from] ||= {} integrated[element.integrated_from][key] = val @@ -297,11 +297,10 @@ super(request, obj) end # Returns true if an element can be loaded joined-in. def can_join?(element) - return false if element.multiple? return false if element.storage != @storage return true end # Generates a select hash description based on the query. @@ -469,64 +468,51 @@ # * joins: an array of structures as returned by #get_join # * remaining_condition: part of the condition which can't be passed to the storage #-- # TODO: better name for :values def prepare_condition(condition, options={}) - # FIXME: move to mapper model = condition.polymorph ? condition.polymorph : @model model_schema = model.mapper.schema cond = {} - # debugger if condition.polymorph - condition.each_with_comparison do |k, v, comp| - # normalize condition values - element = model.elements[k.to_sym] - if (v && !v.is_a?(Condition) && element.model?) - condition.delete(element.name) - def set_pks_condition(condition, el, val, prefix) - el.model.primary_keys.each do |primary_key| - new_prefix = "#{prefix}.#{primary_key.name}" - if (primary_key.model?) - if (primary_key.model.primary_keys.length == 1) - # FIXME: this should not be needed, see below - condition.set(new_prefix, '=', val.get(primary_key).get(primary_key.model.primary_keys[0])) - else - # FIXME! does not work, the subcondition does not get processed - raise "Subconditions on multiple key elements not supported yet" - subcond = Condition.new - set_pks_condition(subcond, primary_key, val.get(primary_key), new_prefix) - condition << subcond - end - else - condition.set(new_prefix, '=', val.get(primary_key)) - end - end - end - if v.is_a?(BaseModel) - set_pks_condition(condition, element, v, element.name) - elsif element.model.primary_keys.length == 1 - new_v = Condition.new - if (model.mapper.have_references?(element.name)) - new_v.set(element.model.primary_keys[0].name, comp, v) - else - new_v.set(element.reverse, comp, v) - end - condition.set(element.name, comp, new_v) - else - raise MapperError, "Value condition passed on #{k}, but #{element.model} has more then one primary key" - end - end - end + bind_values = [] joins = options[:joins] || [] remaining_condition = Condition.new # TODO: implement cond[:conj] = condition.conjunction.to_s cond[:values] = [] + + # find out which elements have non nil conditions to figure out joins + def get_not_nil(model, condition, not_nil) + condition.all_each_with_comparison do |k, v, comp| + next unless k.respond_to?(:to_sym) + element = model.elements[k.to_sym] + next unless element + next unless model.mapper.mapped?(element) + next unless element.model? + not_nil[k] = {} if !v.nil? || comp != '=' + get_not_nil(element.model, v, not_nil[k]) if v.is_a?(Condition) + end + end + + not_nil = options[:not_nil] + unless not_nil + not_nil = {} + get_not_nil(@model, condition, not_nil) + end + condition.each_with_comparison do |k, v, comp| + if k.is_a?(QueryFuncs::Function) + field = prepare_queryfunc(k) + cond[:values] << [field, comp, v] + joins += field.joins + next + end element = model.elements[k.to_sym] next unless model.mapper.mapped?(element) if (element.model?) - if (v && model.mapper.have_references?(element.name) && v.select{ |key, value| !element.model.elements[key].primary_key? }.empty?) + if (v && model.mapper.have_references?(element.name) && v.select{ |key, value| + !element.model.elements[key] || !element.model.elements[key].primary_key? }.empty?) # 1/n <-> 1 with only primary keys element_cond = {:conj => 'AND', :values => []} v.each_with_comparison do |el_k, el_v, el_comp| field = model_schema.qualified_foreign_key_field(element.name, el_k) el_comp ||= '=' @@ -535,15 +521,11 @@ element_cond[:values] << field_cond end cond[:values] << element_cond else if (element.storage == model.mapper.storage) - if v.nil? - join_type = comp == '=' ? :left : :inner - else - join_type = :inner - end + join_type = (v.nil? && comp == '=') ? :left : :inner sub_join = model.mapper.get_join(element, join_type) # FIXME! cleanup, and apply the check to joins acquired in other places, too (maybe pass the current joins to get_join) existent = joins.select{ |j| j[:to] == sub_join[:to] } j_cnt = nil had_join = false @@ -558,21 +540,29 @@ end end sub_join[:as] = "#{sub_join[:to]}#{j_cnt}" if j_cnt joins << sub_join unless had_join - if v.nil? && comp == '=' + if v.nil? && comp == '=' && !not_nil[element.name] element_cond = {:conj => 'AND', :values => []} - element.model.primary_keys.each do |k| - field = model_schema.qualified_foreign_key_field(element.name, k.name) + if model.mapper.have_references?(element.name) + el_name = element.name + el_model = element.model + else + el_model = element.type + model_schema = element.model.mapper.schema + el_name = element.attributes[:junction_their_element] + end + el_model.primary_keys.each do |k| + field = model_schema.qualified_foreign_key_field(el_name, k.name) field_cond = [field, comp, map_condition_value(element.model.elements[k.name].type, nil)] element_cond[:values] << field_cond end cond[:values] << element_cond elsif v - element.model.mapper.prepare_query_condition(v) - sub_condition, sub_joins = element.mapper.prepare_condition(v, :table => sub_join[:as], :joins => joins) + v = element.model.mapper.preprocess_condition(v) + sub_condition, sub_joins = element.mapper.prepare_condition(v, :table => sub_join[:as], :joins => joins, :not_nil => not_nil[element.name]) sub_condition[:table] = sub_join[:as] if sub_join[:as] joins = sub_joins cond[:values] << sub_condition end @@ -595,33 +585,34 @@ end sub_sqls = [] sub_bind_values = [] condition.subconditions.each do |sub| - sub_res = self.prepare_condition(sub, :joins => joins) + sub_res = self.prepare_condition(sub, :joins => joins, :not_nil => not_nil) cond[:values] << sub_res[0] joins = sub_res[1] remaining_condition += sub_res[2] end return [cond, joins, remaining_condition] end + # Figures out a join for element. Returns join hash description, i.e. : # join = { # :type => :inner|:outer|..., # :from => 'table1', # :to => 'table2', # :keys => hash of key pairs, # :condition => join condition # } def get_join(element, join_type = :inner) return unless element.model? - Spider::Logger.debug("Getting join for model #{@model} to element #{element}") - Spider::Logger.debug(@model.primary_keys.map{|k| k.name}) + #Spider::Logger.debug("Getting join for model #{@model} to element #{element}") + #Spider::Logger.debug(@model.primary_keys.map{|k| k.name}) element_table = element.mapper.schema.table if (schema.has_foreign_fields?(element.name)) - Spider::Logger.debug("JOIN A from #{@model} to #{element.name}") + #Spider::Logger.debug("JOIN A from #{@model} to #{element.name}") keys = {} element.model.primary_keys.each do |key| if (key.integrated?) # FIXME raise "Unimplemented join dereference for multiple primary keys" if key.integrated_from.model.primary_keys.length > 1 @@ -629,11 +620,10 @@ else el_field = element.mapper.schema.field(key.name) end fk = schema.foreign_key_field(element.name, key.name) - fk = fk.expression if fk.is_a?(FieldExpression) keys[fk] = el_field # FIXME: works with models as primary keys through a hack in the field method of db_schema, # assuming the model has only one key. the correct way would be to get another join end if (element.condition) @@ -650,21 +640,20 @@ :keys => keys, :condition => condition, :as => as } elsif (element.has_single_reverse? && element.mapper.schema.has_foreign_fields?(element.reverse)) # n/1 <-> n - Spider::Logger.debug("JOIN B from #{@model} to #{element.name}") + #Spider::Logger.debug("JOIN B from #{@model} to #{element.name}") keys = {} @model.primary_keys.each do |key| our_field = nil if (key.integrated?) our_field = schema.foreign_key_field(key.integrated_from.name, key.integrated_from_element) else our_field = schema.field(key.name) end keys[our_field] = element.mapper.schema.foreign_key_field(element.reverse, key.name) - keys[our_field] = keys[our_field].expression if keys[our_field].is_a?(FieldExpression) end if (element.condition) condition, condition_joins, condition_remaining = element.mapper.prepare_condition(element.condition) end join = { @@ -693,10 +682,11 @@ joins = [] el = nil Spider::Logger.debug("GETTING DEEP JOIN TO #{dotted_element} (#{@model})") parts.each do |part| el = current_model.elements[part] + raise "Can't find element #{part} in model #{current_model}" unless el if (el.integrated?) joins << current_model.mapper.get_join(el.integrated_from) current_model = el.integrated_from.type el = current_model.elements[el.integrated_from_element] end @@ -712,10 +702,22 @@ el = current_model.elements[el.integrated_from_element] end return [joins, current_model, el] end + def prepare_queryfunc(func) + joins = [] + func_elements = func.inner_elements + func_elements.each do |el_name, owner_func| + el_joins, el_model, el = get_deep_join(el_name) + joins += el_joins + owner_func.mapper_fields ||= {} + owner_func.mapper_fields[el.name] = el_model.mapper.schema.field(el.name) + end + return FieldFunction.new(storage.function(func), schema.table, joins) + end + # Takes a Spider::QueryFuncs::Expression, and associates the fields to the corresponding elements # Returns an array of needed joins def prepare_expression(expr) joins = [] expr.each_element do |v_el| @@ -735,19 +737,12 @@ fields = [] query.order.each do |order| order_element, direction = order el_model = @model if (order_element.is_a?(QueryFuncs::Function)) - func_fields = [] - func_elements = order_element.inner_elements - func_elements.each do |el_name, owner_func| - el_joins, el_model, el = get_deep_join(el_name) - joins += el_joins - owner_func.mapper_fields ||= {} - owner_func.mapper_fields[el.name] = el_model.mapper.schema.field(el.name) - end - field = storage.function(order_element) + field = prepare_queryfunc(order_element) + joins += field.joins fields << [field, direction] else el_joins, el_model, el = get_deep_join(order_element) if (el.model?) # FIXME: integrated elements @@ -759,9 +754,10 @@ el.model.primary_keys.each do |pk| fields << [schema.qualified_foreign_key_field(el.name, pk.name), direction] end end else + raise "Order on unmapped element #{el_model.name}.#{el.name}" unless el_model.mapper.mapped?(el) field = el_model.mapper.schema.field(el.name) fields << [field, direction] end joins += el_joins end