blacklight_advanced_search 6.0.2 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|