# frozen_string_literal: true # transforms "english like" text queries into a where clause with regex # https://www.postgresql.org/docs/9.5/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES class TextToRegexQuery def initialize(text, fields, default_field, fields_mappings = {}) @text = text.to_s.strip @fields = fields.map(&:to_sym) @default_field = default_field.to_sym @fields_mappings = fields_mappings.merge(@fields.reduce({}) do |mappings, field| table_name, field_name = field.to_s.split(".") mappings[field_name.to_sym] = field mappings end) end def where_clause(query) @cleared_text = @text.dup @column_chunks = [] remove_duplicated_spaces extract_columns escape_special_characters generate_where_clause(query) end private def remove_duplicated_spaces @cleared_text.gsub!(/\s+/, ' ') end def escape_special_characters @cleared_text.gsub!(/\_/, '\_') @cleared_text.tr!('\\', '\\') @cleared_text.gsub!(/%/, '\%') end def extract_columns column_search_term_pairs = @cleared_text.scan(/([A-Za-z0-9_]+:[\w\_-]+)/) @column_chunks = (column_search_term_pairs.flatten.map do |pair| column, term = pair.split(':') next unless @fields_mappings.include?(column.to_sym) @cleared_text.gsub!(pair, '') { @fields_mappings[column.to_sym] => term } end).compact unless @cleared_text.strip.empty? @column_chunks = [{ @default_field.to_s => @cleared_text.strip }] + @column_chunks end @column_chunks end def generate_where_clause(query) where_clause = '' columns = @column_chunks.map { |c| c.keys.first } values = @column_chunks.map { |c| c.values.first } columns.each do |column| quoted_column = '"' + column.to_s.gsub(".",'"."') + '"' where_clause += "#{quoted_column} ILIKE ? OR " end where_clause += " 1<>1 " regexed_values = values.map { |v| "%#{v}%" } query.where([where_clause] + regexed_values) end end