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