From fe96f682db4ecbe14032ca7430528fda6939226a Mon Sep 17 00:00:00 2001 From: Bilal Date: Wed, 29 Apr 2020 07:21:39 +0200 Subject: [PATCH] start using HAVING instead of manually building joins --- lib/pg_searchable_regex.rb | 6 ++-- lib/text_to_sql_query.rb | 65 +++----------------------------------- 2 files changed, 7 insertions(+), 64 deletions(-) diff --git a/lib/pg_searchable_regex.rb b/lib/pg_searchable_regex.rb index 576e9e5..1fd390b 100644 --- a/lib/pg_searchable_regex.rb +++ b/lib/pg_searchable_regex.rb @@ -55,16 +55,14 @@ module PgSearchable def ts_search(value) return if @ts_search_fields.blank? || value.blank? - model = ancestors.first sql_query_object = TextToSqlQuery.new( value, @ts_search_fields, @default_fields, @ts_search_fields_mappings, - @ts_joins, - model + @ts_joins ) - joins(sql_query_object.join_clause).where(sql_query_object.where_clause).distinct + distinct.joins(sql_query_object.join_clause).group(:id).having(sql_query_object.where_clause) end def should_update_cache_field? diff --git a/lib/text_to_sql_query.rb b/lib/text_to_sql_query.rb index 416d182..a2dc9bd 100644 --- a/lib/text_to_sql_query.rb +++ b/lib/text_to_sql_query.rb @@ -1,7 +1,7 @@ require_relative 'parser' class TextToSqlQuery - def initialize(text, fields, default_fields, fields_mappings = {}, joins = [], model = nil) + def initialize(text, fields, default_fields, fields_mappings = {}, joins = []) @text = text.to_s.strip @fields = fields.map(&:to_sym) @@ -21,7 +21,6 @@ class TextToSqlQuery @fields_mappings[field] = value if @fields_mappings[field] end @joins = joins - @model = model end def where_clause @@ -31,20 +30,12 @@ class TextToSqlQuery end def join_clause - return if @joins.empty? + return nil if @joins.empty? - table_column_mappings - model_association_mappings - - join_clause_part = '' - @joins.each do |join| - join_sql_part = generate_join_sql_part_for join - join_clause_part += join_sql_part - end - join_clause_part + return *@joins end - private +private def generate_sql(tree) first_key = tree.keys.first @@ -83,7 +74,7 @@ class TextToSqlQuery if mapping.nil? raise "Unknown field '#{first_key.to_s}'" else - ["CAST(#{mapping.to_s} AS TEXT) ILIKE ?", "%#{escaped_node_value}%"] + ["STRING_AGG(CAST(#{mapping.to_s} AS TEXT), '') ILIKE ?", "%#{escaped_node_value}%"] end end end @@ -120,50 +111,4 @@ class TextToSqlQuery result.gsub!(/%/, '\%') result end - - def table_column_mappings - @table_column_mappings = {} - @fields_mappings.each_value do |table_with_column| - split_names = table_with_column.to_s.split '.' - table_name = split_names.first - column_name = split_names.second - @table_column_mappings[table_name] = [] if @table_column_mappings[table_name].nil? - @table_column_mappings[table_name] << column_name - end - @table_column_mappings - end - - def model_association_mappings - @model_associations = {} - @model.reflect_on_all_associations.each do |association| - name = association.name - - @model_associations[name] = { - option_as: association.options[:as] || name, - type: association.type - } - end - @model_associations - end - - def generate_join_sql_part_for(join) - association_data = @model_associations[join] - join_table_name = join.to_s - raise "Join table #{join_table_name} has no association data" if association_data.nil? - - select_sql_part = '' - columns_for_table = @table_column_mappings[join_table_name] || [] - - # TODO: Can be optimized - do not include columns that are not referenced in user query - columns_for_table.each do |column_name| - select_sql_part += "string_agg(#{column_name}, '') AS #{column_name}, " - end - - option_as = association_data[:option_as] - type = association_data[:type] - model_name = @model.to_s - table_name = @model.table_name - - "LEFT JOIN (SELECT #{option_as}_id, #{select_sql_part} #{type} FROM #{join_table_name} GROUP BY #{option_as}_id, #{type}) #{join_table_name} on #{join_table_name}.#{option_as}_id = #{table_name}.id AND #{join_table_name}.#{type} = '#{model_name}'" - end end