require_relative 'parser' class TextToSqlQuery 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) fields_mappings.each do |field, value| @fields_mappings[field] = value if @fields_mappings[field] end end def where_clause @parser = Query.new @parsed_tree = @parser.parse(@text) generate_sql @parsed_tree end private def generate_sql(tree) first_key = tree.keys.first node_value = tree[first_key] case first_key when :DEFAULT_COLUMN escaped_node_value = handle_special_chars node_value ["#{@default_field.to_s} ILIKE ?", "%#{escaped_node_value}%"] when :OPERATOR_OR generate_expression_for_logical_operator(:OR, node_value) when :OPERATOR_AND generate_expression_for_logical_operator(:AND, node_value) when :OPERATOR_NOT not_array = generate_sql node_value if not_array.length < 2 raise "There should be more than 1 element for expression following NOT operator" end not_expression = not_array.shift not_params = not_array ["NOT #{not_expression}"] + not_params else # key is column name escaped_node_value = handle_special_chars node_value mapping = @fields_mappings[first_key.to_sym] if mapping.nil? raise "Unknown field '#{first_key.to_s}'" else ["#{mapping.to_s} ILIKE ?", "%#{escaped_node_value}%"] end end end def generate_expression_for_logical_operator(operator, operator_array) if operator_array.length != 2 raise "There should be two array elements for #{operator.to_s} operator" end first_operand = generate_sql operator_array.first second_operand = generate_sql operator_array.last if first_operand.length < 2 raise 'There should be more than 1 element in first operand array' end if second_operand.length < 2 raise 'There should be more than 1 element in second operand array' end first_operand_expression = first_operand.shift first_operand_params = first_operand second_operand_expression = second_operand.shift second_operand_params = second_operand ["(#{first_operand_expression} #{operator.to_s} #{second_operand_expression})"] + first_operand_params + second_operand_params end def handle_special_chars(text) result = text.gsub(/\"/, '') result.gsub!(/\_/, '\_') result.tr!('\\', '\\') result.gsub!(/%/, '\%') result end end