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