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