search_query_transformer.rb 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. # frozen_string_literal: true
  2. class SearchQueryTransformer < Parslet::Transform
  3. class Query
  4. attr_reader :should_clauses, :must_not_clauses, :must_clauses, :filter_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. @filter_clauses = grouped.fetch(:filter, [])
  11. end
  12. def apply(search)
  13. should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) }
  14. must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) }
  15. must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) }
  16. filter_clauses.each { |clause| search = search.filter(**clause_to_filter(clause)) }
  17. search.query.minimum_should_match(1)
  18. end
  19. private
  20. def clause_to_query(clause)
  21. case clause
  22. when TermClause
  23. { multi_match: { type: 'most_fields', query: clause.term, fields: ['text', 'text.stemmed'] } }
  24. when PhraseClause
  25. { match_phrase: { text: { query: clause.phrase } } }
  26. else
  27. raise "Unexpected clause type: #{clause}"
  28. end
  29. end
  30. def clause_to_filter(clause)
  31. case clause
  32. when PrefixClause
  33. { term: { clause.filter => clause.term } }
  34. else
  35. raise "Unexpected clause type: #{clause}"
  36. end
  37. end
  38. end
  39. class Operator
  40. class << self
  41. def symbol(str)
  42. case str
  43. when '+'
  44. :must
  45. when '-'
  46. :must_not
  47. when nil
  48. :should
  49. else
  50. raise "Unknown operator: #{str}"
  51. end
  52. end
  53. end
  54. end
  55. class TermClause
  56. attr_reader :prefix, :operator, :term
  57. def initialize(prefix, operator, term)
  58. @prefix = prefix
  59. @operator = Operator.symbol(operator)
  60. @term = term
  61. end
  62. end
  63. class PhraseClause
  64. attr_reader :prefix, :operator, :phrase
  65. def initialize(prefix, operator, phrase)
  66. @prefix = prefix
  67. @operator = Operator.symbol(operator)
  68. @phrase = phrase
  69. end
  70. end
  71. class PrefixClause
  72. attr_reader :filter, :operator, :term
  73. def initialize(prefix, term)
  74. @operator = :filter
  75. case prefix
  76. when 'from'
  77. @filter = :account_id
  78. username, domain = term.gsub(/\A@/, '').split('@')
  79. domain = nil if TagManager.instance.local_domain?(domain)
  80. account = Account.find_remote!(username, domain)
  81. @term = account.id
  82. else
  83. raise Mastodon::SyntaxError
  84. end
  85. end
  86. end
  87. rule(clause: subtree(:clause)) do
  88. prefix = clause[:prefix][:term].to_s if clause[:prefix]
  89. operator = clause[:operator]&.to_s
  90. if clause[:prefix]
  91. PrefixClause.new(prefix, clause[:term].to_s)
  92. elsif clause[:term]
  93. TermClause.new(prefix, operator, clause[:term].to_s)
  94. elsif clause[:shortcode]
  95. TermClause.new(prefix, operator, ":#{clause[:term]}:")
  96. elsif clause[:phrase]
  97. PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s)
  98. else
  99. raise "Unexpected clause type: #{clause}"
  100. end
  101. end
  102. rule(query: sequence(:clauses)) { Query.new(clauses) }
  103. end