module ScopedSearch::QueryBuilder::Field
This module gets included into the Field
class to add SQL generation.
Public Instance Methods
This method construct join statement for a key value table It assume the following table structure
+----------+ +---------+ +--------+ | main | | value | | key | | main_pk | | main_fk | | | | | | key_fk | | key_pk | +----------+ +---------+ +--------+
uniq name for the joins are needed in case that there is more than one condition on different keys in the same query.
# File lib/scoped_search/query_builder.rb 330 def construct_join_sql(key_relation, num) 331 join_sql = "" 332 connection = klass.connection 333 key = key_relation.to_s.singularize.to_sym 334 335 key_table = definition.reflection_by_name(klass, key).table_name 336 value_table = klass.table_name.to_s 337 338 value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key)) 339 340 main_reflection = definition.reflection_by_name(definition.klass, relation) 341 if main_reflection 342 main_table = definition.klass.table_name 343 main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation)) 344 345 join_sql = "\n INNER JOIN #{connection.quote_table_name(value_table)} #{value_table}_#{num} ON (#{main_table}.#{main_table_pk} = #{value_table}_#{num}.#{value_table_fk_main})" 346 value_table = " #{value_table}_#{num}" 347 end 348 join_sql += "\n INNER JOIN #{connection.quote_table_name(key_table)} #{key_table}_#{num} ON (#{key_table}_#{num}.#{key_table_pk} = #{value_table}.#{value_table_fk_key}) " 349 350 return join_sql 351 end
This method construct join statement for a key value table It assume the following table structure
+----------+ +---------+ | main | | key | | main_pk | | value | | | | main_fk | +----------+ +---------+
uniq name for the joins are needed in case that there is more than one condition on different keys in the same query.
# File lib/scoped_search/query_builder.rb 362 def construct_simple_join_sql(num) 363 connection = klass.connection 364 key_value_table = klass.table_name 365 366 main_table = definition.klass.table_name 367 main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation)) 368 369 join_sql = "\n INNER JOIN #{connection.quote_table_name(key_value_table)} #{key_value_table}_#{num} ON (#{connection.quote_table_name(main_table)}.#{connection.quote_column_name(main_table_pk)} = #{key_value_table}_#{num}.#{connection.quote_column_name(value_table_fk_main)})" 370 return join_sql 371 end
# File lib/scoped_search/query_builder.rb 381 def reflection_conditions(reflection) 382 return unless reflection 383 conditions = reflection.options[:conditions] 384 conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type] 385 conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if reflection.options[:polymorphic] 386 " AND #{conditions}" if conditions 387 end
# File lib/scoped_search/query_builder.rb 373 def reflection_keys(reflection) 374 pk = reflection.klass.primary_key 375 fk = reflection.options[:foreign_key] 376 # activerecord prior to 3.1 doesn't respond to foreign_key method and hold the key name in the reflection primary key 377 fk ||= reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name 378 reflection.macro == :belongs_to ? [fk, pk] : [pk, fk] 379 end
# File lib/scoped_search/query_builder.rb 389 def to_ext_method_sql(key, operator, value, &block) 390 raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method) 391 conditions = definition.klass.send(ext_method.to_sym,key, operator, value) rescue {} 392 raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash) 393 sql = '' 394 conditions.map do |notification, content| 395 case notification 396 when :include then yield(:include, content) 397 when :joins then yield(:joins, content) 398 when :conditions then sql = content 399 when :parameter then content.map{|c| yield(:parameter, c)} 400 end 401 end 402 return sql 403 end
Return an SQL representation for this field. Also make sure that the relation which includes the search field is included in the SQL query.
This function may yield an :include that should be used in the ActiveRecord::Base#find call, to make sure that the field is available for the SQL query.
# File lib/scoped_search/query_builder.rb 300 def to_sql(operator = nil, &block) # :yields: finder_option_type, value 301 num = rand(1000000) 302 connection = klass.connection 303 if key_relation 304 yield(:joins, construct_join_sql(key_relation, num) ) 305 yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?") 306 klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name 307 return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}" 308 elsif key_field 309 yield(:joins, construct_simple_join_sql(num)) 310 yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?") 311 klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name 312 return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}" 313 elsif relation 314 yield(:include, relation) 315 end 316 column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s) 317 column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset 318 column_name 319 end