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 354 def construct_join_sql(key_relation, num) 355 join_sql = "" 356 connection = klass.connection 357 key = key_relation.to_s.singularize.to_sym 358 359 key_table = definition.reflection_by_name(klass, key).table_name 360 value_table = klass.table_name.to_s 361 362 value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key)) 363 364 main_reflection = definition.reflection_by_name(definition.klass, relation) 365 if main_reflection 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(value_table)} #{value_table}_#{num} ON (#{main_table}.#{main_table_pk} = #{value_table}_#{num}.#{value_table_fk_main})" 370 value_table = " #{value_table}_#{num}" 371 end 372 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}) " 373 374 return join_sql 375 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 386 def construct_simple_join_sql(num) 387 connection = klass.connection 388 key_value_table = klass.table_name 389 390 main_table = definition.klass.table_name 391 main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation)) 392 393 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)})" 394 return join_sql 395 end
# File lib/scoped_search/query_builder.rb 405 def reflection_conditions(reflection) 406 return unless reflection 407 conditions = reflection.options[:conditions] 408 conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type] 409 conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if reflection.options[:polymorphic] 410 " AND #{conditions}" if conditions 411 end
# File lib/scoped_search/query_builder.rb 397 def reflection_keys(reflection) 398 pk = reflection.klass.primary_key 399 fk = reflection.options[:foreign_key] 400 # activerecord prior to 3.1 doesn't respond to foreign_key method and hold the key name in the reflection primary key 401 fk ||= reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name 402 reflection.macro == :belongs_to ? [fk, pk] : [pk, fk] 403 end
# File lib/scoped_search/query_builder.rb 413 def to_ext_method_sql(key, operator, value, &block) 414 raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method) 415 begin 416 conditions = definition.klass.send(ext_method.to_sym, key, operator, value) 417 rescue StandardError => e 418 raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' failed with error: #{e}" 419 end 420 raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash) 421 sql = '' 422 conditions.map do |notification, content| 423 case notification 424 when :include then yield(:include, content) 425 when :joins then yield(:joins, content) 426 when :conditions then sql = content 427 when :parameter then content.map{|c| yield(:parameter, c)} 428 end 429 end 430 return sql 431 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 324 def to_sql(operator = nil, &block) # :yields: finder_option_type, value 325 num = rand(1000000) 326 connection = klass.connection 327 if key_relation 328 yield(:joins, construct_join_sql(key_relation, num) ) 329 yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?") 330 klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name 331 return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}" 332 elsif key_field 333 yield(:joins, construct_simple_join_sql(num)) 334 yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?") 335 klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name 336 return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}" 337 elsif relation 338 yield(:include, relation) 339 end 340 column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s) 341 column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset 342 column_name 343 end