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