search_query_transformer.rb 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. # frozen_string_literal: true
  2. class SearchQueryTransformer < Parslet::Transform
  3. class Query
  4. attr_reader :should_clauses, :must_not_clauses, :must_clauses
  5. def initialize(clauses)
  6. grouped = clauses.chunk(&:operator).to_h
  7. @should_clauses = grouped.fetch(:should, [])
  8. @must_not_clauses = grouped.fetch(:must_not, [])
  9. @must_clauses = grouped.fetch(:must, [])
  10. end
  11. def apply(search)
  12. should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) }
  13. must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) }
  14. must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) }
  15. search.query.minimum_should_match(1)
  16. end
  17. private
  18. def clause_to_query(clause)
  19. case clause
  20. when TermClause
  21. { multi_match: { type: 'most_fields', query: clause.term, fields: ['text', 'text.stemmed'] } }
  22. when PhraseClause
  23. { match_phrase: { text: { query: clause.phrase } } }
  24. else
  25. raise "Unexpected clause type: #{clause}"
  26. end
  27. end
  28. end
  29. class Operator
  30. class << self
  31. def symbol(str)
  32. case str
  33. when '+'
  34. :must
  35. when '-'
  36. :must_not
  37. when nil
  38. :should
  39. else
  40. raise "Unknown operator: #{str}"
  41. end
  42. end
  43. end
  44. end
  45. class TermClause
  46. attr_reader :prefix, :operator, :term
  47. def initialize(prefix, operator, term)
  48. @prefix = prefix
  49. @operator = Operator.symbol(operator)
  50. @term = term
  51. end
  52. end
  53. class PhraseClause
  54. attr_reader :prefix, :operator, :phrase
  55. def initialize(prefix, operator, phrase)
  56. @prefix = prefix
  57. @operator = Operator.symbol(operator)
  58. @phrase = phrase
  59. end
  60. end
  61. rule(clause: subtree(:clause)) do
  62. prefix = clause[:prefix][:term].to_s if clause[:prefix]
  63. operator = clause[:operator]&.to_s
  64. if clause[:term]
  65. TermClause.new(prefix, operator, clause[:term].to_s)
  66. elsif clause[:shortcode]
  67. TermClause.new(prefix, operator, ":#{clause[:term]}:")
  68. elsif clause[:phrase]
  69. PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s)
  70. else
  71. raise "Unexpected clause type: #{clause}"
  72. end
  73. end
  74. rule(query: sequence(:clauses)) { Query.new(clauses) }
  75. end