lib/spiderfw/model/mappers/db_mapper.rb in spiderfw-0.6.21 vs lib/spiderfw/model/mappers/db_mapper.rb in spiderfw-0.6.22
- old
+ new
@@ -88,47 +88,50 @@
values = {}
obj.no_autoload do
@model.each_element do |element|
next if !mapped?(element) || element.integrated?
next if save_mode == :update && !obj.element_modified?(element)
- if (save_mode == :insert)
+ if save_mode == :insert
if element.attributes[:autoincrement] && !schema.attributes(element.name)[:autoincrement]
obj.set(element.name, @storage.sequence_next(schema.sequence(element.name)))
end
end
- if (!element.multiple?)
- next if (save_mode == :update && element.primary_key?)
- next if (element.model? && !schema.has_foreign_fields?(element.name))
- element_val = obj.get(element)
- # next if (element.model? && (!(element_val = obj.get(element)) || !))
- next if (element.integrated?)
- element_val = nil if element.model? && element_val.is_a?(BaseModel) && !element_val.primary_keys_set?
- if (element.model?)
- element.model.primary_keys.each do |key|
- # FIXME! only works with one primary key
- if (key.model?)
- key_type = key.model.primary_keys[0].type
- key_value = element_val ? element_val.get(key.name).get(key.model.primary_keys[0]) : nil
- else
- key_type = key.model? ? key.model.primary_keys[0].type : key.type
- key_value = element_val ? element_val.get(key.name) : nil
- end
- store_key = schema.foreign_key_field(element.name, key.name)
- next if store_key.is_a?(FieldExpression)
- values[store_key] = map_save_value(key_type, key_value, save_mode)
- end
- else
- store_key = schema.field(element.name)
- values[store_key] = map_save_value(element.type, element_val, save_mode)
- end
- end
+ next if element.multiple?
+ next if save_mode == :update && element.primary_key?
+ next if element.model? && !schema.has_foreign_fields?(element.name)
+
+ element_val = obj.get(element)
+ prepare_save_value(element, element_val, save_mode, values)
end
end
return {
:values => values
}
end
+
+ def prepare_save_value(element, element_val, save_mode, values={})
+ element_val = nil if element.model? && element_val.is_a?(BaseModel) && !element_val.primary_keys_set?
+ if element.model?
+ element.model.primary_keys.each do |key|
+ # FIXME! only works with one primary key
+ if (key.model?)
+ key_type = key.model.primary_keys[0].type
+ key_value = element_val ? element_val.get(key.name).get(key.model.primary_keys[0]) : nil
+ else
+ key_type = key.model? ? key.model.primary_keys[0].type : key.type
+ key_value = element_val ? element_val.get(key.name) : nil
+ end
+ store_key = schema.foreign_key_field(element.name, key.name)
+ next if store_key.is_a?(FieldExpression)
+ values[store_key] = map_save_value(key_type, key_value, save_mode)
+ end
+ else
+ store_key = schema.field(element.name)
+ values[store_key] = map_save_value(element.type, element_val, save_mode)
+ end
+ values
+ end
# Insert preprocessing
def prepare_insert(obj) #:nodoc:
save = prepare_save(obj, :insert)
return nil unless save[:values].length > 0
@@ -150,44 +153,48 @@
save[:table] = @schema.table
return @storage.sql_update(save)
end
# Updates according to a condition, storing the values, which must passed as a Hash.
+ # Condition may be nil.
def bulk_update(values, condition)
db_values = {}
joins = []
integrated = {}
- condition = preprocess_condition(condition)
+ condition = preprocess_condition(condition) if condition
values.each do |key, val|
element = @model.elements[key]
- if (element.integrated?)
+ if element.integrated?
integrated[element.integrated_from] ||= {}
integrated[element.integrated_from][key] = val
next
end
next if !mapped?(element)
- next if element.model? && val != nil
- store_key = schema.field(element.name)
- next unless store_key
- if (val.is_a?(Spider::QueryFuncs::Expression))
+ next if element.multiple?
+ next if element.model? && !schema.has_foreign_fields?(element.name)
+ if val.is_a?(Spider::QueryFuncs::Expression)
joins += prepare_expression(val)
+ store_key = schema.field(element.name)
+ next unless store_key
db_values[store_key] = val
else
- db_values[store_key] = map_save_value(element.type, val, :update)
+ prepare_save_value(element, val, :update, db_values)
end
end
integrated.each do |i_el, i_values|
next unless condition[i_el.name]
i_el.mapper.bulk_update(i_values, condition[i_el.name]) # FIXME?
end
return if db_values.empty?
save = {:table => schema.table, :values => db_values}
- condition, c_joins = prepare_condition(condition)
- joins += c_joins
- save[:condition] = condition
+ if condition
+ condition, c_joins = prepare_condition(condition)
+ joins += c_joins
+ save[:condition] = condition
+ end
save[:joins] = prepare_joins(joins)
- sql, bind_vars = @storage.sql_update(save)
+ sql, bind_vars = @storage.sql_update(save, true)
return @storage.execute(sql, *bind_vars)
end
# Lock db
#--
@@ -250,11 +257,15 @@
request = Request.new
@model.elements_array.each{ |el| request.request(el.name) }
end
model = obj_or_model.is_a?(Class) ? obj_or_model : obj_or_model.model
data = {}
+ extra_data = {}
request.keys.each do |element_name|
+ if element_name.is_a?(QueryFuncs::SelectFunction)
+ extra_data[element_name.as] = result[element_name.as.to_s]
+ end
element = @model.elements[element_name]
result_value = nil
next if !element || element.integrated? || !have_references?(element)
if (element.model? && schema.has_foreign_fields?(element.name))
pks = {}
@@ -280,10 +291,13 @@
Spider::Logger.warn("Row in DB without primary keys for model #{model}; won't be mapped:")
Spider::Logger.warn(data)
return nil
end
data.keys.each{ |el| obj.element_loaded(el) }
+ extra_data.each do |k, v|
+ obj[k] = v
+ end
if (request.polymorphs)
request.polymorphs.each do |model, polym_request|
polym_result = {}
polym_request.keys.each do |element_name|
field = model.mapper.schema.field(element_name).name
@@ -313,11 +327,13 @@
end
# Generates a select hash description based on the query.
def prepare_select(query) #:nodoc:
condition, joins = prepare_condition(query.condition)
- elements = query.request.keys.select{ |k| mapped?(k) }
+ elements = query.request.keys.select{ |k| !k.is_a?(QueryFuncs::SelectFunction) && mapped?(k) }
+ select_functions = query.request.keys.select{ |k| k.is_a?(QueryFuncs::SelectFunction) }
+
keys = []
primary_keys = []
types = {}
if (query.limit && query.order.empty? && !query.only_one?)
@model.primary_keys.each do |key|
@@ -329,10 +345,12 @@
cnt = 0
order_joins.each do |oj|
oj[:as] ||= "ORD#{cnt+=1}" if joins.select{ |j| j[:to] == oj[:to] }.length > 0
end
joins += order_joins if order_joins
+ group_by, group_by_joins = prepare_group_by(query)
+ joins += group_by_joins if group_by_joins
seen_fields = {}
model_pks = []
@model.primary_keys.each do |pk|
if (pk.integrated?)
model_pks << pk.integrated_from.name
@@ -361,19 +379,15 @@
seen_fields[field.name] = true
end
end
end
sub_request = query.request[element.name]
- # if (can_join?(element) && sub_request.is_a?(Request) &&
- # sub_request.select{|k, v| !element.model.elements[k].primary_key?}.length > 0)
- # sub_request = element.mapper.prepare_query_request(sub_request).reject{ |name, req| element.reverse == name }
- # sub_select = element.mapper.prepare_select(Query.new(nil, sub_request))
- # keys += sub_select[:keys]
- # joins << get_join(element)
- # end
end
end
+ select_functions.each do |f|
+ keys << prepare_queryfunc(f)
+ end
if (query.request.polymorphs? || !query.condition.polymorphs.empty?)
only_conditions = {:conj => 'or', :values => []} if (query.request.only_polymorphs?)
polymorphs = (query.request.polymorphs.keys + query.condition.polymorphs).uniq
polymorphs.each do |model|
polym_request = query.request.polymorphs[model] || Request.new
@@ -425,10 +439,11 @@
:primary_keys => primary_keys.uniq,
:tables => tables,
:condition => condition,
:joins => joins,
:order => order,
+ :group_by => group_by,
:offset => query.offset,
:limit => query.limit
}
end
@@ -578,13 +593,14 @@
if k.is_a?(QueryFuncs::Function)
field = prepare_queryfunc(k)
cond[:values] << [field, comp, v]
joins += field.joins
if is_having
- cond[:group_by_fields] += k.inner_elements.map{ |el_name, owner_func|
- owner_func.mapper_fields[el_name.to_s]
- }
+ k.inner_elements.each do |el_name, owner_func|
+ next if owner_func.is_a?(QueryFuncs::AggregateFunction)
+ cond[:group_by_fields] <<= owner_func.mapper_fields[el_name.to_s]
+ end
end
next
end
element = model.elements[k.to_sym]
next unless model.mapper.mapped?(element)
@@ -598,63 +614,70 @@
if v && model.mapper.have_references?(element.name) && v.primary_keys_only?(element.model)
# 1/n <-> 1 with only primary keys
element_cond = {:conj => 'AND', :values => [], :is_having => is_having}
v.each_with_comparison do |el_k, el_v, el_comp|
field = model_schema.foreign_key_field(element.name, el_k)
+ next if field.is_a?(FixedExpression)
el_comp ||= '='
op = el_comp
field_cond = [field, op, map_condition_value(element.model.elements[el_k.to_sym].type, el_v)]
element_cond[:values] << field_cond
cond[:group_by_fields] << field if is_having
end
cond[:values] << element_cond
else
if element.storage == model.mapper.storage
- join_type = join_info[element.name.to_s] ? :inner : :left
- 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
- existent.each do |j|
- if sub_join[:to] == j[:to] && sub_join[:keys] == j[:keys] && sub_join[:conditions] == j[:conditions]
- # if any condition allows a left join, then a left join should be used here as well
- j[:type] = :left if sub_join[:type] == :left
- sub_join = j
- had_join = true
- break
- else
- j_cnt ||= 0; j_cnt += 1
- end
- end
- sub_join[:as] = "#{sub_join[:to]}#{j_cnt}" if j_cnt
- joins << sub_join unless had_join
+ needs_join = true
if v.nil? && comp == '='
el_model_schema = model_schema
element_cond = {:conj => 'AND', :values => [], :is_having => is_having}
if model.mapper.have_references?(element.name)
el_name = element.name
el_model = element.model
+ needs_join = false
elsif element.junction?
el_model = element.type
el_model_schema = element.model.mapper.schema
el_name = element.attributes[:junction_their_element]
else
- el_model = element.type
- el_model_schema = el_model.mapper.schema
+ el_model = @model
+ el_model_schema = element.type.mapper.schema
el_name = element.reverse
end
el_model.primary_keys.each do |k|
field = el_model_schema.foreign_key_field(el_name, k.name)
- field_cond = [field, comp, map_condition_value(element.model.elements[k.name].type, nil)]
+ next if field.is_a?(FixedExpression)
+ field_cond = [field, comp, map_condition_value(el_model.elements[k.name].type, nil)]
element_cond[:values] << field_cond
element_cond[:is_having] = is_having
cond[:group_by_fields] << field if is_having
end
cond[:values] << element_cond
- elsif v
+ end
+ if needs_join
+ join_type = join_info[element.name.to_s] ? :inner : :left
+ 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
+ existent.each do |j|
+ if sub_join[:to] == j[:to] && sub_join[:keys] == j[:keys] && sub_join[:conditions] == j[:conditions]
+ # if any condition allows a left join, then a left join should be used here as well
+ j[:type] = :left if sub_join[:type] == :left
+ sub_join = j
+ had_join = true
+ break
+ else
+ j_cnt ||= 0; j_cnt += 1
+ end
+ end
+ sub_join[:as] = "#{sub_join[:to]}#{j_cnt}" if j_cnt
+ joins << sub_join unless had_join
+ end
+ if v
sub_condition, sub_joins = element.mapper.prepare_condition(v, :table => sub_join[:as],
:joins => joins, :join_info => el_join_info, :is_having => is_having || nil)
sub_condition[:table] = sub_join[:as] if sub_join[:as]
joins = sub_joins
cond[:values] << sub_condition
@@ -665,11 +688,11 @@
remaining_condition.set(k, comp, v)
end
end
elsif(model_schema.field(element.name))
field = model_schema.field(element.name)
- field = FieldInAliasedTable(field, options[:table]) if options[:table]
+ field = FieldInAliasedTable.new(field, options[:table]) if options[:table]
op = comp ? comp : '='
if (v.is_a?(Spider::QueryFuncs::Expression))
v_joins = prepare_expression(v)
joins += v_joins
cond[:values] << [field, op, v]
@@ -813,10 +836,11 @@
joins += el_joins
owner_func.mapper_fields ||= {}
owner_func.mapper_fields[el_name.to_s] = el_model.mapper.schema.field(el.name)
end
f = FieldFunction.new(storage.function(func), schema.table, joins)
+ f.as = func.as if func.is_a?(QueryFuncs::SelectFunction)
f.aggregate = true if func.has_aggregates?
return f
end
# Takes a Spider::QueryFuncs::Expression, and associates the fields to the corresponding elements
@@ -848,11 +872,13 @@
else
el_joins, el_model, el = get_deep_join(order_element)
if el.model?
if el_model.mapper.have_references?(el) || el.model.storage != storage
el.model.primary_keys.each do |pk|
- fields << [el_model.mapper.schema.foreign_key_field(el.name, pk.name), direction]
+ field = el_model.mapper.schema.foreign_key_field(el.name, pk.name)
+ next if field.is_a?(FixedExpression)
+ fields << [field, direction]
end
else
el.model.primary_keys.each do |pk|
fields << [el.model.mapper.schema.field(pk.name), direction]
end
@@ -866,10 +892,38 @@
end
end
return [fields, joins]
end
+ def prepare_group_by(query)
+ return nil if !query.group_by_elements
+ joins = []
+ fields = []
+ query.group_by_elements.each do |gb|
+ el_model = @model
+ el_joins, el_model, el = get_deep_join(gb)
+ # FIXME: this is almost identical to prepare_order
+ if el.model?
+ if el_model.mapper.have_references?(el) || el.model.storage != storage
+ el.model.primary_keys.each do |pk|
+ fields << el_model.mapper.schema.foreign_key_field(el.name, pk.name)
+ end
+ else
+ el.model.primary_keys.each do |pk|
+ fields << el.model.mapper.schema.field(pk.name)
+ 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
+ end
+ joins += el_joins
+ end
+ return [fields, joins]
+ end
+
# Returns a type accepted by the storage for type.
def map_type(type)
st = type
return Fixnum if st <= Spider::DataTypes::PK
while (st && !storage.class.base_types.include?(st))
@@ -1058,19 +1112,23 @@
else
super
end
end
- # Autogenerates schema. Returns a DbSchema.
- def generate_schema(schema=nil)
- had_schema = schema ? true : false
- schema ||= DbSchema.new
+ def schema_table_name
n = @model.name.sub('::Models', '')
app = @model.app
app_name = app.name if app
short_prefix = app.short_prefix if app
n.sub!(app_name, short_prefix) if short_prefix
- schema.table ||= @model.attributes[:db_table] || @storage.table_name(n)
+ @storage.table_name(n)
+ end
+
+ # Autogenerates schema. Returns a DbSchema.
+ def generate_schema(schema=nil)
+ had_schema = schema ? true : false
+ schema ||= DbSchema.new
+ schema.table ||= @model.attributes[:db_table] || schema_table_name
integrated_pks = []
@model.each_element do |element|
if element.integrated?
integrated_pks << [element.integrated_from.name, element.integrated_from_element] if (element.primary_key?)
end