blacklight_advanced_search 6.0.2 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +351 -0
- data/.solr_wrapper.yml +5 -0
- data/.travis.yml +4 -7
- data/Gemfile +18 -11
- data/Rakefile +24 -34
- data/VERSION +1 -1
- data/app/controllers/advanced_controller.rb +5 -7
- data/app/controllers/blacklight_advanced_search/advanced_controller.rb +5 -8
- data/app/helpers/advanced_helper.rb +4 -6
- data/blacklight_advanced_search.gemspec +11 -8
- data/lib/blacklight_advanced_search.rb +29 -34
- data/lib/blacklight_advanced_search/advanced_query_parser.rb +12 -13
- data/lib/blacklight_advanced_search/advanced_search_builder.rb +28 -32
- data/lib/blacklight_advanced_search/catalog_helper_override.rb +11 -34
- data/lib/blacklight_advanced_search/controller.rb +1 -1
- data/lib/blacklight_advanced_search/filter_parser.rb +7 -9
- data/lib/blacklight_advanced_search/parsing_nesting_parser.rb +5 -8
- data/lib/blacklight_advanced_search/redirect_legacy_params_filter.rb +23 -25
- data/lib/blacklight_advanced_search/render_constraints_override.rb +46 -33
- data/lib/blacklight_advanced_search/version.rb +0 -1
- data/lib/generators/blacklight_advanced_search/assets_generator.rb +4 -8
- data/lib/generators/blacklight_advanced_search/blacklight_advanced_search_generator.rb +0 -2
- data/lib/generators/blacklight_advanced_search/install_generator.rb +9 -5
- data/lib/generators/blacklight_advanced_search/templates/advanced_controller.rb +0 -2
- data/lib/parsing_nesting/grammar.rb +22 -25
- data/lib/parsing_nesting/tree.rb +156 -168
- data/solr/conf/_rest_managed.json +3 -0
- data/solr/conf/admin-extra.html +31 -0
- data/solr/conf/elevate.xml +36 -0
- data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr/conf/protwords.txt +21 -0
- data/solr/conf/schema.xml +635 -0
- data/solr/conf/scripts.conf +24 -0
- data/solr/conf/solrconfig.xml +411 -0
- data/solr/conf/spellings.txt +2 -0
- data/solr/conf/stopwords.txt +58 -0
- data/solr/conf/stopwords_en.txt +58 -0
- data/solr/conf/synonyms.txt +31 -0
- data/solr/conf/xslt/example.xsl +132 -0
- data/solr/conf/xslt/example_atom.xsl +67 -0
- data/solr/conf/xslt/example_rss.xsl +66 -0
- data/solr/conf/xslt/luke.xsl +337 -0
- data/solr/sample_solr_documents.yml +2692 -0
- data/spec/features/blacklight_advanced_search_form_spec.rb +0 -2
- data/spec/helpers/advanced_helper_spec.rb +0 -2
- data/spec/integration/blacklight_stub_spec.rb +0 -2
- data/spec/lib/advanced_search_builder_spec.rb +7 -14
- data/spec/lib/blacklight_advanced_search/render_constraints_override_spec.rb +39 -0
- data/spec/lib/deep_merge_spec.rb +109 -34
- data/spec/lib/filter_parser_spec.rb +8 -14
- data/spec/parsing_nesting/build_tree_spec.rb +73 -81
- data/spec/parsing_nesting/consuming_spec.rb +2 -12
- data/spec/parsing_nesting/to_solr_spec.rb +93 -130
- data/spec/spec_helper.rb +0 -3
- data/spec/test_app_templates/app/controllers/catalog_controller.rb +3 -3
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +3 -3
- metadata +63 -13
- data/spec/spec.opts +0 -4
data/lib/parsing_nesting/tree.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
require 'parsing_nesting/grammar'
|
2
2
|
module ParsingNesting::Tree
|
3
|
-
|
4
3
|
# Get parslet output for string (parslet output is json-y objects), and
|
5
4
|
# transform to an actual abstract syntax tree made up of more semantic
|
6
|
-
# ruby objects, Node's. The top one will always be a List.
|
7
|
-
#
|
5
|
+
# ruby objects, Node's. The top one will always be a List.
|
6
|
+
#
|
8
7
|
# Call #to_query on resulting Node in order to transform to Solr query,
|
9
8
|
# optionally passing in Solr params to be used as LocalParams in nested
|
10
|
-
# dismax queries.
|
9
|
+
# dismax queries.
|
11
10
|
#
|
12
11
|
# Our approach here works, but as we have to put in special cases
|
13
12
|
# it starts getting messy. Ideally we might want to actually transform
|
14
13
|
# the Object graph (abstract syntax tree) instead of trying to handle
|
15
|
-
# special cases in #to_query.
|
14
|
+
# special cases in #to_query.
|
16
15
|
# For instance, transform object graph for a problematic pure-negative
|
17
16
|
# clause to the corresponding object graph without that (-a AND -b) ==>
|
18
17
|
# (NOT (a OR b). Transform (NOT NOT a) to (a). That would probably be
|
@@ -21,117 +20,115 @@ module ParsingNesting::Tree
|
|
21
20
|
# multiple levels. But it's working for now.
|
22
21
|
#
|
23
22
|
# the #negate method was an experiment in transforming parse tree in
|
24
|
-
# place, but isn't being used. But it's left as a sign post.
|
25
|
-
def self.parse(string, query_parser='dismax')
|
23
|
+
# place, but isn't being used. But it's left as a sign post.
|
24
|
+
def self.parse(string, query_parser = 'dismax')
|
26
25
|
to_node_tree(ParsingNesting::Grammar.new.parse(string), query_parser)
|
27
26
|
end
|
28
|
-
|
29
27
|
|
30
28
|
# theoretically Parslet's Transform could be used for this, but I think the
|
31
29
|
# manner in which I'm parsing to Parslet labelled hash isn't exactly what
|
32
30
|
# Parslet Transform is set up to work with, I couldn't figure it out. But
|
33
31
|
# easy enough to do 'manually'.
|
34
32
|
def self.to_node_tree(tree, query_parser)
|
35
|
-
if tree.
|
33
|
+
if tree.is_a? Array
|
36
34
|
# at one point I was normalizing top-level lists of one item to just
|
37
35
|
# be that item, no list wrapper. But having the list wrapper
|
38
|
-
# at the top level is actually useful for Solr output.
|
39
|
-
List.new(
|
40
|
-
elsif tree.
|
36
|
+
# at the top level is actually useful for Solr output.
|
37
|
+
List.new(tree.collect { |i| to_node_tree(i, query_parser) }, query_parser)
|
38
|
+
elsif tree.is_a? Hash
|
41
39
|
if list = tree[:list]
|
42
|
-
List.new(
|
40
|
+
List.new(list.collect { |i| to_node_tree(i, query_parser) }, query_parser)
|
43
41
|
elsif tree.has_key?(:and_list)
|
44
|
-
AndList.new(
|
42
|
+
AndList.new(tree[:and_list].collect { |i| to_node_tree(i, query_parser) }, query_parser)
|
45
43
|
elsif tree.has_key?(:or_list)
|
46
|
-
OrList.new(
|
44
|
+
OrList.new(tree[:or_list].collect { |i| to_node_tree(i, query_parser) }, query_parser)
|
47
45
|
elsif not_payload = tree[:not_expression]
|
48
|
-
NotExpression.new(
|
46
|
+
NotExpression.new(to_node_tree(not_payload, query_parser))
|
49
47
|
elsif tree.has_key?(:mandatory)
|
50
|
-
MandatoryClause.new(
|
48
|
+
MandatoryClause.new(to_node_tree(tree[:mandatory], query_parser))
|
51
49
|
elsif tree.has_key?(:excluded)
|
52
|
-
ExcludedClause.new(
|
50
|
+
ExcludedClause.new(to_node_tree(tree[:excluded], query_parser))
|
53
51
|
elsif phrase = tree[:phrase]
|
54
|
-
Phrase.new(
|
52
|
+
Phrase.new(phrase)
|
55
53
|
elsif tree.has_key?(:token)
|
56
|
-
Term.new(
|
54
|
+
Term.new(tree[:token].to_s)
|
57
55
|
end
|
58
56
|
end
|
59
57
|
end
|
60
|
-
|
58
|
+
|
61
59
|
class Node
|
62
60
|
# this default to_query works well for anything that is embeddable in
|
63
|
-
# a standard way.
|
64
|
-
# non-embeddable nodes will have to override and do it different.
|
61
|
+
# a standard way.
|
62
|
+
# non-embeddable nodes will have to override and do it different.
|
65
63
|
def to_query(solr_params)
|
66
64
|
build_nested_query([self], solr_params)
|
67
65
|
end
|
68
|
-
|
66
|
+
|
69
67
|
protected # some utility methods
|
70
|
-
|
68
|
+
|
71
69
|
# Builds a query from a list of Node's that have #to_embed, and some
|
72
70
|
# solr params to embed as LocalParams.
|
73
71
|
#
|
74
72
|
# By default will create a nested _query_, handling escaping appropriately.
|
75
|
-
# but pass in :always_nested=>false, and it will sometimes be an ordinary
|
76
|
-
# query where possible. (possibly still with LocalParams).
|
73
|
+
# but pass in :always_nested=>false, and it will sometimes be an ordinary
|
74
|
+
# query where possible. (possibly still with LocalParams).
|
77
75
|
#
|
78
76
|
# LocalParams will be default have "!dismax" added to them, but set
|
79
77
|
# :force_deftype to something else (or nil) if you want.
|
80
78
|
#
|
81
79
|
# Also takes care of simple "pure negative" queries like "-one -two",
|
82
80
|
# converting them to a nested NOT query that will be handled appropriately.
|
83
|
-
# those simple negatives can't be handled right by dismax otherwise.
|
84
|
-
def build_nested_query(embeddables, solr_params={}, options = {})
|
85
|
-
options = {:always_nested => true,
|
86
|
-
:force_deftype => "dismax"}.merge(options)
|
87
|
-
|
81
|
+
# those simple negatives can't be handled right by dismax otherwise.
|
82
|
+
def build_nested_query(embeddables, solr_params = {}, options = {})
|
83
|
+
options = { :always_nested => true,
|
84
|
+
:force_deftype => "dismax" }.merge(options)
|
85
|
+
|
88
86
|
# if it's pure negative, we need to transform
|
89
|
-
if embeddables.find_all{|n| n.
|
90
|
-
negated = NotExpression.new(
|
91
|
-
solr_params = solr_params.merge(:mm => "1")
|
92
|
-
return negated.to_query(solr_params)
|
87
|
+
if embeddables.find_all { |n| n.is_a?(ExcludedClause) }.length == embeddables.length
|
88
|
+
negated = NotExpression.new(List.new(embeddables.collect { |n| n.operand }, options[:force_deftype]))
|
89
|
+
solr_params = solr_params.merge(:mm => "1")
|
90
|
+
return negated.to_query(solr_params)
|
93
91
|
else
|
94
|
-
|
95
|
-
inner_query = build_local_params(solr_params, options[:force_deftype]) +
|
96
|
-
embeddables.collect {|n| n.to_embed}.join(" ")
|
97
|
-
|
98
|
-
if options[:always_nested]
|
92
|
+
|
93
|
+
inner_query = build_local_params(solr_params, options[:force_deftype]) +
|
94
|
+
embeddables.collect { |n| n.to_embed }.join(" ")
|
95
|
+
|
96
|
+
if options[:always_nested]
|
99
97
|
return '_query_:"' + bs_escape(inner_query) + '"'
|
100
98
|
else
|
101
99
|
return inner_query
|
102
100
|
end
|
103
101
|
|
104
|
-
end
|
102
|
+
end
|
105
103
|
end
|
106
104
|
|
107
105
|
# Pass in nil 2nd argument if you DON'T want to embed
|
108
106
|
# "!dismax" in your local params. Used by #to_single_query_params
|
109
107
|
def build_local_params(hash = {}, force_deftype = "dismax")
|
110
108
|
# we insist on dismax for our embedded queries, or whatever
|
111
|
-
# other defType supplied in 2nd argument.
|
109
|
+
# other defType supplied in 2nd argument.
|
112
110
|
hash = hash.dup
|
113
111
|
if force_deftype
|
114
112
|
hash[:defType] = force_deftype
|
115
113
|
hash.delete("defType") # avoid weird colision with hard to debug results
|
116
|
-
end
|
117
|
-
|
118
|
-
if
|
114
|
+
end
|
115
|
+
|
116
|
+
if !hash.empty?
|
119
117
|
defType = hash.delete(:defType) || hash.delete("defType")
|
120
|
-
"{!" + (defType ? "#{defType} " : "") +
|
118
|
+
"{!" + (defType ? "#{defType} " : "") + hash.collect { |k, v| "#{k}=#{v.to_s.include?(" ") ? "'" + v + "'" : v}" }.join(" ") + "}"
|
121
119
|
else
|
122
|
-
#no local params!
|
120
|
+
# no local params!
|
123
121
|
""
|
124
122
|
end
|
125
123
|
end
|
126
|
-
|
127
|
-
def bs_escape(val, char='"')
|
124
|
+
|
125
|
+
def bs_escape(val, char = '"')
|
128
126
|
# crazy double escaping to actually get a single backslash
|
129
127
|
# in there without triggering regexp capture reference
|
130
128
|
val.gsub(char, '\\\\' + char)
|
131
129
|
end
|
132
130
|
end
|
133
|
-
|
134
|
-
|
131
|
+
|
135
132
|
class List < Node
|
136
133
|
attr_accessor :list
|
137
134
|
attr_reader :query_parser
|
@@ -139,143 +136,139 @@ module ParsingNesting::Tree
|
|
139
136
|
@query_parser = query_parser
|
140
137
|
self.list = aList
|
141
138
|
end
|
139
|
+
|
142
140
|
def can_embed?
|
143
141
|
false
|
144
142
|
end
|
145
|
-
|
146
|
-
def simple_pure_negative?
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
def to_query(solr_params={})
|
143
|
+
|
144
|
+
def simple_pure_negative?
|
145
|
+
list.find_all { |i| i.is_a? ExcludedClause }.length == list.length
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_query(solr_params = {})
|
151
149
|
queries = []
|
152
|
-
|
153
|
-
(embeddable, gen_full_query) = list.partition {|i| i.respond_to?(:can_embed?) && i.can_embed?}
|
154
|
-
|
150
|
+
|
151
|
+
(embeddable, gen_full_query) = list.partition { |i| i.respond_to?(:can_embed?) && i.can_embed? }
|
152
|
+
|
155
153
|
unless embeddable.empty?
|
156
154
|
queries << build_nested_query(embeddable, solr_params, force_deftype: query_parser)
|
157
155
|
end
|
158
|
-
|
156
|
+
|
159
157
|
gen_full_query.each do |node|
|
160
158
|
queries << node.to_query(solr_params)
|
161
159
|
end
|
162
|
-
|
160
|
+
|
163
161
|
queries.join(" AND ")
|
164
162
|
end
|
165
163
|
|
166
164
|
# Returns a Hash, assumes this will be the ONLY :q, used for
|
167
165
|
# parsing 'simple search' to Solr. Pass in params that need to
|
168
|
-
# be LOCAL solr params (using "{foo=bar}" embedded in query).
|
166
|
+
# be LOCAL solr params (using "{foo=bar}" embedded in query).
|
169
167
|
# Params that should be sent to Solr seperately are caller's responsibility,
|
170
|
-
# merge em into the returned hash.
|
168
|
+
# merge em into the returned hash.
|
171
169
|
#
|
172
170
|
# For very simple queries, this will produce an ordinary Solr q
|
173
171
|
# much like would be produced ordinarily. But for AND/OR/NOT, will
|
174
|
-
# sometimes include multiple nested queries instead.
|
172
|
+
# sometimes include multiple nested queries instead.
|
175
173
|
#
|
176
174
|
# This method will still sometimes return a single nested _query_, that
|
177
175
|
# could theoretically really be ordinary query possibly with localparams.
|
178
176
|
# It still works, but isn't optimizing for a simpler query, because
|
179
177
|
# it's using much of the same code used for combining multiple fields
|
180
178
|
# that need nested queries. Maybe we'll optimize later, but the code
|
181
|
-
# gets tricky.
|
179
|
+
# gets tricky.
|
182
180
|
def to_single_query_params(solr_local_params)
|
183
181
|
# Can it be expressed in a single dismax?
|
184
|
-
|
185
|
-
if list.find_all {|i| i.respond_to?(:can_embed?) && i.can_embed? }.length == list.length
|
186
|
-
{
|
187
|
-
#build_local_params(solr_local_params, nil) + list.collect {|n| n.to_embed}.join(" "),
|
188
|
-
:q => build_nested_query(list, solr_local_params, :always_nested => false, :force_deftype => nil),
|
182
|
+
|
183
|
+
if list.find_all { |i| i.respond_to?(:can_embed?) && i.can_embed? }.length == list.length
|
184
|
+
{
|
185
|
+
# build_local_params(solr_local_params, nil) + list.collect {|n| n.to_embed}.join(" "),
|
186
|
+
:q => build_nested_query(list, solr_local_params, :always_nested => false, :force_deftype => nil),
|
189
187
|
:defType => query_parser
|
190
|
-
}
|
188
|
+
}
|
191
189
|
else
|
192
190
|
# Can't be expressed in a single dismax, do it the normal way
|
193
|
-
{
|
191
|
+
{
|
194
192
|
:q => self.to_query(solr_local_params),
|
195
|
-
:defType => "lucene"
|
193
|
+
:defType => "lucene"
|
196
194
|
}
|
197
195
|
end
|
198
196
|
end
|
199
|
-
|
197
|
+
|
200
198
|
def negate
|
201
|
-
List.new(list.collect {|i| i.negate})
|
199
|
+
List.new(list.collect { |i| i.negate })
|
202
200
|
end
|
203
201
|
end
|
204
|
-
|
202
|
+
|
205
203
|
class AndList < List
|
206
|
-
|
207
204
|
# We make an and-list embeddable only if all it's elements
|
208
205
|
# are embeddable, then no problem we just embed them all
|
209
206
|
# as Solr '+' mandatory, and achieve the AND.
|
210
207
|
# For now, pure negative is considered not embeddable, although
|
211
208
|
# theoretically it could sometimes be embedded if transformed
|
212
|
-
# properly.
|
209
|
+
# properly.
|
213
210
|
def can_embed?
|
214
|
-
|
211
|
+
!simple_pure_negative? && !list.collect { |i| i.can_embed? }.include?(false)
|
215
212
|
end
|
216
|
-
|
213
|
+
|
217
214
|
# Only if all operands are embeddable.
|
218
215
|
# Trick is if they were bare terms/phrases, we add a '+' on
|
219
216
|
# front, but if they already were +/-, then we don't need to,
|
220
|
-
# and leaving them along will have desired semantics.
|
217
|
+
# and leaving them along will have desired semantics.
|
221
218
|
# This works even on "-", because dismax mm seems to not consider "-"
|
222
|
-
# clauses, they are always required regardless of mm.
|
219
|
+
# clauses, they are always required regardless of mm.
|
223
220
|
def to_embed
|
224
221
|
list.collect do |operand|
|
225
222
|
s = operand.to_embed
|
226
223
|
if s =~ /^\+/ || s =~ /^\-/
|
227
224
|
s
|
228
225
|
else
|
229
|
-
'+'+s
|
226
|
+
'+' + s
|
230
227
|
end
|
231
228
|
end.join(" ")
|
232
229
|
end
|
233
|
-
|
230
|
+
|
234
231
|
# for those that aren't embeddable, or pure negative
|
235
232
|
def to_query(local_params)
|
236
233
|
if simple_pure_negative?
|
237
234
|
# Can do it in one single nested dismax, if we're simple arguments
|
238
|
-
# that are pure negative.
|
235
|
+
# that are pure negative.
|
239
236
|
# build_nested_query will handle negating the pure negative for
|
240
|
-
# us.
|
237
|
+
# us.
|
241
238
|
build_nested_query(list, local_params)
|
242
|
-
else
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
239
|
+
else
|
240
|
+
"( " +
|
241
|
+
list.collect do |i|
|
242
|
+
i.to_query(local_params)
|
243
|
+
end.join(" AND ") +
|
244
|
+
" )"
|
248
245
|
end
|
249
246
|
end
|
250
|
-
|
251
|
-
# convent logical property here, not(a AND b) === not(a) OR not(b)
|
247
|
+
|
248
|
+
# convent logical property here, not(a AND b) === not(a) OR not(b)
|
252
249
|
def negate
|
253
|
-
OrList.new(
|
250
|
+
OrList.new(list.collect { |n| n.negate })
|
254
251
|
end
|
255
|
-
|
256
252
|
end
|
257
|
-
|
258
|
-
|
259
|
-
class OrList < List
|
260
|
-
|
253
|
+
|
254
|
+
class OrList < List
|
261
255
|
# never embeddable
|
262
256
|
def can_embed?
|
263
257
|
false
|
264
258
|
end
|
265
|
-
|
266
|
-
|
259
|
+
|
267
260
|
def to_query(local_params)
|
268
261
|
# Okay, we're never embeddable as such, but sometimes we can
|
269
262
|
# turn our operands into one single nested dismax query with mm=1, when
|
270
263
|
# all our operands are 'simple', other times we need to actually do
|
271
|
-
# two seperate nested queries seperated by lucene OR.
|
264
|
+
# two seperate nested queries seperated by lucene OR.
|
272
265
|
# If all our children are embeddable but _not_ an "AndList", we can
|
273
266
|
# do the one query part. The AndList is theoretically embeddable, but
|
274
|
-
# not in a way compatible with flattening an OR to one query.
|
267
|
+
# not in a way compatible with flattening an OR to one query.
|
275
268
|
# Sorry, this part is one of the least clean part of this code!
|
276
269
|
|
277
|
-
not_flattenable = list.find {|i| !
|
278
|
-
|
270
|
+
not_flattenable = list.find { |i| !(i.can_embed? && !i.is_a?(AndList)) }
|
271
|
+
|
279
272
|
if not_flattenable
|
280
273
|
to_multi_queries(local_params)
|
281
274
|
elsif simple_pure_negative?
|
@@ -284,156 +277,153 @@ module ParsingNesting::Tree
|
|
284
277
|
to_one_dismax_query(local_params)
|
285
278
|
end
|
286
279
|
end
|
287
|
-
|
280
|
+
|
288
281
|
# build_nested_query isn't smart enough to handle refactoring
|
289
|
-
# a simple pure negative "OR", that needs an mm of 100%.
|
282
|
+
# a simple pure negative "OR", that needs an mm of 100%.
|
290
283
|
# Let's just do it ourselves. What we're doing makes more sense
|
291
284
|
# if you remember that:
|
292
285
|
# -a OR -b === NOT (a AND b)
|
293
286
|
def to_simple_pure_negative_query(local_params)
|
294
287
|
# take em out of their ExcludedClauses
|
295
|
-
embeddables = list.collect {|n| n.operand}
|
288
|
+
embeddables = list.collect { |n| n.operand }
|
296
289
|
# and insist on mm 100%
|
297
290
|
solr_params = local_params.merge(:mm => "100%")
|
298
|
-
|
299
|
-
# and put the NOT in front to preserve semantics.
|
300
|
-
|
301
|
-
bs_escape(build_local_params(solr_params) +
|
302
|
-
embeddables.collect {|n| n.to_embed}.join(" ")) +
|
291
|
+
|
292
|
+
# and put the NOT in front to preserve semantics.
|
293
|
+
'NOT _query_:"' +
|
294
|
+
bs_escape(build_local_params(solr_params) +
|
295
|
+
embeddables.collect { |n| n.to_embed }.join(" ")) +
|
303
296
|
'"'
|
304
297
|
end
|
305
|
-
|
306
|
-
# all our arguments are 'simple' (terms and phrases with +/-),
|
307
|
-
# put am all in one single dismax with mm forced to 1.
|
308
|
-
def to_one_dismax_query(local_params)
|
298
|
+
|
299
|
+
# all our arguments are 'simple' (terms and phrases with +/-),
|
300
|
+
# put am all in one single dismax with mm forced to 1.
|
301
|
+
def to_one_dismax_query(local_params)
|
309
302
|
build_nested_query(list, local_params.merge(:mm => "1"))
|
310
303
|
end
|
311
|
-
|
312
|
-
def to_multi_queries(local_params)
|
313
|
-
"( " +
|
304
|
+
|
305
|
+
def to_multi_queries(local_params)
|
306
|
+
"( " +
|
314
307
|
list.collect do |i|
|
315
|
-
|
308
|
+
if i.is_a?(NotExpression) || (i.respond_to?(:simple_pure_negative?) && i.simple_pure_negative?)
|
316
309
|
# need special handling to work around Solr 1.4.1's lack of handling
|
317
310
|
# of pure negative in an OR
|
318
311
|
"(*:* AND #{i.to_query(local_params)})"
|
319
312
|
else
|
320
313
|
i.to_query(local_params)
|
321
|
-
|
314
|
+
end
|
322
315
|
end.join(" OR ") +
|
323
|
-
" )"
|
316
|
+
" )"
|
324
317
|
end
|
325
|
-
|
318
|
+
|
326
319
|
# convenient logical property here, not(a OR b) === not(a) AND not(b)
|
327
320
|
def negate
|
328
|
-
AndList.new(
|
321
|
+
AndList.new(list.collect { |n| n.negate })
|
329
322
|
end
|
330
|
-
|
331
323
|
end
|
332
|
-
|
333
|
-
|
334
|
-
class NotExpression
|
324
|
+
|
325
|
+
class NotExpression
|
335
326
|
def initialize(exp)
|
336
327
|
self.operand = exp
|
337
328
|
end
|
338
329
|
attr_accessor :operand
|
339
|
-
|
330
|
+
|
340
331
|
# We have to do the weird thing with *:* AND NOT (real thing), because
|
341
332
|
# Solr 1.4.1 seems not to be able to handle "x OR NOT y" otherwise, at least
|
342
333
|
# in some cases, but does fine with
|
343
334
|
# "x OR (*:* AND NOT y)", which should mean the same thing.
|
344
335
|
def to_query(solr_params)
|
345
336
|
# rescue double-nots to not treat them crazy-like and make the query
|
346
|
-
# more work for Solr than it needs to be with a double-negative.
|
347
|
-
if operand.
|
337
|
+
# more work for Solr than it needs to be with a double-negative.
|
338
|
+
if operand.is_a?(NotExpression)
|
348
339
|
operand.operand.to_query(solr_params)
|
349
340
|
else
|
350
341
|
"NOT " + operand.to_query(solr_params)
|
351
342
|
end
|
352
343
|
end
|
353
|
-
|
344
|
+
|
354
345
|
def can_embed?
|
355
346
|
false
|
356
347
|
end
|
357
|
-
|
348
|
+
|
358
349
|
def negate
|
359
350
|
operand
|
360
351
|
end
|
361
352
|
end
|
362
|
-
|
353
|
+
|
363
354
|
class MandatoryClause < Node
|
364
355
|
attr_accessor :operand
|
365
356
|
def initialize(v)
|
366
357
|
self.operand = v
|
367
358
|
end
|
368
|
-
|
359
|
+
|
369
360
|
def can_embed?
|
370
|
-
#right now '+' clauses only apply to terms/phrases
|
371
|
-
#which we can embed with a + in front.
|
361
|
+
# right now '+' clauses only apply to terms/phrases
|
362
|
+
# which we can embed with a + in front.
|
372
363
|
true
|
373
364
|
end
|
365
|
+
|
374
366
|
def to_embed
|
375
367
|
'+' + operand.to_embed
|
376
368
|
end
|
377
369
|
|
378
370
|
# negating mandatory to excluded is decent semantics, although
|
379
|
-
# it's not strictly 'true', it's a choice.
|
371
|
+
# it's not strictly 'true', it's a choice.
|
380
372
|
def negate
|
381
|
-
ExcludedClause.new(
|
373
|
+
ExcludedClause.new(operand)
|
382
374
|
end
|
383
375
|
end
|
384
|
-
|
376
|
+
|
385
377
|
class ExcludedClause < Node
|
386
378
|
attr_accessor :operand
|
387
|
-
|
379
|
+
|
388
380
|
def initialize(v)
|
389
381
|
self.operand = v
|
390
|
-
end
|
391
|
-
|
382
|
+
end
|
383
|
+
|
392
384
|
def can_embed?
|
393
|
-
#right now '-' clauses only apply to terms/phrases, which
|
394
|
-
#we can embed with a '-' in front.
|
385
|
+
# right now '-' clauses only apply to terms/phrases, which
|
386
|
+
# we can embed with a '-' in front.
|
395
387
|
true
|
396
388
|
end
|
397
|
-
|
389
|
+
|
398
390
|
def to_embed
|
399
391
|
'-' + operand.to_embed
|
400
392
|
end
|
401
|
-
|
393
|
+
|
402
394
|
# negating excluded to mandatory is a pretty decent choice
|
403
395
|
def negate
|
404
|
-
MandatoryClause.new(
|
396
|
+
MandatoryClause.new(operand)
|
405
397
|
end
|
406
|
-
|
398
|
+
|
407
399
|
def simple_pure_negative?
|
408
400
|
true
|
409
401
|
end
|
410
|
-
|
411
402
|
end
|
412
|
-
|
413
|
-
|
403
|
+
|
414
404
|
class Phrase < Node
|
415
405
|
attr_accessor :value
|
416
|
-
|
406
|
+
|
417
407
|
def initialize(string)
|
418
408
|
self.value = string
|
419
409
|
end
|
420
|
-
|
410
|
+
|
421
411
|
def can_embed?
|
422
412
|
true
|
423
413
|
end
|
424
|
-
|
414
|
+
|
425
415
|
def to_embed
|
426
416
|
'"' + value + '"'
|
427
417
|
end
|
428
|
-
|
418
|
+
|
429
419
|
def negate
|
430
420
|
ExcludedClause.new(self)
|
431
421
|
end
|
432
422
|
end
|
433
|
-
|
423
|
+
|
434
424
|
class Term < Node
|
435
|
-
attr_accessor :value
|
436
|
-
|
425
|
+
attr_accessor :value
|
426
|
+
|
437
427
|
def initialize(string)
|
438
428
|
self.value = string
|
439
429
|
end
|
@@ -441,15 +431,13 @@ module ParsingNesting::Tree
|
|
441
431
|
def can_embed?
|
442
432
|
true
|
443
433
|
end
|
444
|
-
|
434
|
+
|
445
435
|
def to_embed
|
446
436
|
value
|
447
|
-
end
|
448
|
-
|
437
|
+
end
|
438
|
+
|
449
439
|
def negate
|
450
440
|
ExcludedClause.new(self)
|
451
441
|
end
|
452
442
|
end
|
453
443
|
end
|
454
|
-
|
455
|
-
|