blacklight_cql 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +142 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/app/controllers/blacklight_cql/explain_controller.rb +17 -0
- data/app/helpers/blacklight_cql/explain_helper.rb +44 -0
- data/app/views/blacklight_cql/explain/explain.xml.builder +30 -0
- data/config/routes.rb +10 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/blacklight_cql/blacklight_to_solr.rb +115 -0
- data/lib/blacklight_cql/solr_helper_extension.rb +24 -0
- data/lib/blacklight_cql/template_helper_extension.rb +19 -0
- data/lib/blacklight_cql.rb +25 -0
- data/rails/init.rb +32 -0
- data/spec/app_root/app/controllers/application_controller.rb +2 -0
- data/spec/app_root/config/boot.rb +115 -0
- data/spec/app_root/config/database.yml +31 -0
- data/spec/app_root/config/environment.rb +30 -0
- data/spec/app_root/config/environments/in_memory.rb +0 -0
- data/spec/app_root/config/environments/mysql.rb +0 -0
- data/spec/app_root/config/environments/postgresql.rb +0 -0
- data/spec/app_root/config/environments/sqlite.rb +0 -0
- data/spec/app_root/config/environments/sqlite3.rb +0 -0
- data/spec/app_root/config/routes.rb +4 -0
- data/spec/app_root/lib/blacklight/search_fields.rb +97 -0
- data/spec/app_root/lib/console_with_fixtures.rb +4 -0
- data/spec/blacklight_to_solr_spec.rb +50 -0
- data/spec/data/luke.yaml +75 -0
- data/spec/data/zeerex-2.0.xsd +482 -0
- data/spec/marc_data/chomsky.mrc +1 -0
- data/spec/marc_data/social_journal.mrc +1 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +73 -0
- data/spec/views/explain.xml.builder_spec.rb +125 -0
- data/tasks/blacklight_cql_tasks.rake +4 -0
- data/uninstall.rb +1 -0
- metadata +146 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
= Blacklight-cql
|
2
|
+
|
3
|
+
An extension for the Blacklight solr search front-end.
|
4
|
+
|
5
|
+
http://projectblacklight.org
|
6
|
+
|
7
|
+
http://github.com/projectblacklight/blacklight
|
8
|
+
|
9
|
+
Provides for CQL search queries that map to Solr and Blacklight fields.
|
10
|
+
|
11
|
+
|
12
|
+
= Installation
|
13
|
+
|
14
|
+
Add to your local app's environment.rb:
|
15
|
+
config.gem "blacklight_cql"
|
16
|
+
|
17
|
+
Run "rake gems:install". That's it, you now have CQL support in your Blacklight.
|
18
|
+
|
19
|
+
See below for optional configuration to change default behavior.
|
20
|
+
|
21
|
+
= Use
|
22
|
+
|
23
|
+
http://your.blacklight.com/catalog?search_field=cql&q=[uri-encoded cql query]
|
24
|
+
|
25
|
+
Or for an atom response for instance,
|
26
|
+
|
27
|
+
http://your.blacklight.com/catalog.atom?search_field=cql&q=[uri-encoded cql query]
|
28
|
+
|
29
|
+
See http://www.loc.gov/standards/sru/specs/cql.html for more info on CQL syntax and semantics.
|
30
|
+
|
31
|
+
Any search_field you have configured in Blacklight.config[:search_fields] (probably in your config/blacklight_config.rb) is available as a CQL index. These search fields are only available with the custom "solr.dismax" CQL relation, taking a CQL expression as a value.
|
32
|
+
|
33
|
+
some_field solr.dismax "term +required -negate \"a phrase\" "
|
34
|
+
|
35
|
+
For dismax fields, the "=" server-choice relation means the same thing:
|
36
|
+
|
37
|
+
some_field = "term +required -negate \"a phrase\" "
|
38
|
+
|
39
|
+
Any Solr indexed field is also available as a CQL index. A much greater range of CQL relations are supported when you specify a Solr indexed field directly.
|
40
|
+
|
41
|
+
solr_field cql.adj "some phrase" AND solr_year within "1990 2000"
|
42
|
+
|
43
|
+
Solr indexed field CQL support is provided by the cql_ruby gem, for details on relations supported see: http://cql-ruby.rubyforge.org/svn/trunk/lib/cql_ruby/cql_to_solr.rb
|
44
|
+
|
45
|
+
If there is a direct solr indexed field with the same name as a Blacklight-configured dismax field, the BL field will take precedence. You can use explicit CQL "context set" prefixes to disambiguate.
|
46
|
+
|
47
|
+
[lsolr.field]
|
48
|
+
"lsolr" prefix means a direct solr indexed field
|
49
|
+
[local.some_field]
|
50
|
+
"local" prefix means a dismax field configured in Blacklight.config[:search_fields]
|
51
|
+
|
52
|
+
These prefixes can be changed, see configuration below.
|
53
|
+
|
54
|
+
Raw solr fields and Blacklight config'ed fields CAN be mixed together in a single CQL query.
|
55
|
+
|
56
|
+
(lsolr.title_t any "one two three" AND lsolr.author_t all "smith john") OR local.title = "my dismax title query"
|
57
|
+
|
58
|
+
CQL *does* need to be URL escaped in a URL, of course:
|
59
|
+
|
60
|
+
http://some.blacklight.com/catalog.atom?search_field=cql&q=%28lsolr.title_t+any+%22one+two+three%22+AND+lsolr.author_t+all+%22smith+john%22%29+OR+local.title+%3D+%22my+dismax+title+query%22
|
61
|
+
|
62
|
+
For "solr.dismax" or "=" relations, the the cql.serverChoice index maps to your default blacklight-configed field and solr.dismax relation. Otherwise, it maps to a direct solr field query with "adj", and the solr indexed field generally ends up being "text", although this depends on your configuration.
|
63
|
+
|
64
|
+
= SRU/ZeeRex Explain
|
65
|
+
|
66
|
+
This plugin does *not* provide a full SRU server. However, a ZeeRex/SRU explain document is provided by the plugin to advertise, in machine-readable format, what CQL indexes (ie, search fields) are provided by the server, and what relations are supported on each search field.
|
67
|
+
|
68
|
+
The explain document can be found at /catalog/explain on your server. (This URL end-point is not presently configurable).
|
69
|
+
|
70
|
+
For solr fields themselves, the plugin finds them via a Solr luke request, looking for any field that is Indexed in solr, and advertising it. (If you have configured lucene indexes directly not through solr, they will likely be erroneously included in the explain as well).
|
71
|
+
|
72
|
+
For Blacklight fields, Blacklight.config[:search_fields] is used to discover fields to put in the Explain.
|
73
|
+
|
74
|
+
Note that at present only the custom solr.dismax relation is supported on Blacklight fields. Most of the standard CQL relations are supported on raw solr fields.
|
75
|
+
|
76
|
+
|
77
|
+
= Configuration
|
78
|
+
|
79
|
+
== URL cql key, and cql label
|
80
|
+
|
81
|
+
A psuedo-blacklight-search-field is added by the Cql plugin to indicate a CQL search in the URL and BL processing. You can change the definition of this psuedo-field however you want to change the URL search_field key, the label for a CQL search echoed back to the user in HTML, or even to add some additional Solr parameters for the top-level Solr query for CQL searches. The value is a hash with the same semantics as other Blacklight.config[:search_fields] elements.
|
82
|
+
|
83
|
+
In an initializer:
|
84
|
+
BlacklightCql::SolrHelperExtension.psuedo_search_field = {
|
85
|
+
:key => "super_search",
|
86
|
+
:display_label => "The Super Search",
|
87
|
+
:solr_parameters => { "mm" => "100%" }
|
88
|
+
}
|
89
|
+
|
90
|
+
== Dismax search field configuration
|
91
|
+
|
92
|
+
All fields configured in Blacklight.config[:search_fields] are available as CQL indexes. If you'd like to make more dismax-configured search fields available in CQL but not the standard HTML search select menu, simply add them with :show_in_simple_select = false, eg:
|
93
|
+
|
94
|
+
Blacklight.config[:search_fields] << {:key => "only_in_cql", :show_in_simple_select => false, :local_solr_parameters => { :qf => "$my_special_qf"}}
|
95
|
+
|
96
|
+
As in the example above, you may want to use :local_solr_parameters referencing dollar-sign parameters that will be defined in your solrconfig.xml and de-referenced by Solr. This will keep your CQL-generated Solr querries a lot more readable in your logs and debugging. However, this feature is not available in Blacklight 2.5 (edge, or future 2.6).
|
97
|
+
|
98
|
+
Simply supplying literal values in :solr_paramaters is also supported and will work fine, it will just result in very long search querries in your solr query log.
|
99
|
+
|
100
|
+
== CQL context set prefixes
|
101
|
+
|
102
|
+
You can change the CQL "context set" prefix used for specifying a CQL index that is a direct solr field, or a Blacklight dismax configured field. In a Rails initializer:
|
103
|
+
|
104
|
+
CqlRuby.to_solr_defaults[:solr_field_prefix] = "my_solr"
|
105
|
+
CqlRuby.to_solr_defaults[:blacklight_field_prefix] = "my_blacklight_fields"
|
106
|
+
|
107
|
+
== Defaults from CqlRuby for direct solr indexed field querries.
|
108
|
+
|
109
|
+
For direct-solr-field operations, there are additional defaults that can be set, supported by CqlRuby. See: http://cql-ruby.rubyforge.org/svn/trunk/lib/cql_ruby/cql_to_solr.rb
|
110
|
+
|
111
|
+
Eg:
|
112
|
+
|
113
|
+
CqlRuby.to_solr_defaults[:default_index] = "solr_index"
|
114
|
+
CqlRuby.to_solr_defaults[:all_index] = "solr_mega_index"
|
115
|
+
CqlRuby.to_solr_defaults[:default_relation] = "cql.any"
|
116
|
+
|
117
|
+
= TO DO
|
118
|
+
|
119
|
+
* Support more CQL relations on blacklight dismax fields. Right now only dismax queries are supported. We could also support:
|
120
|
+
* cql.all (set mm to 100%)
|
121
|
+
* cql.adj (phrase search with qs set to 0)
|
122
|
+
* cql.any
|
123
|
+
* range querries on dismax fields? Maybe. <, <=, >, >=, within
|
124
|
+
* <> on dismax fields, similar to how it works on raw solr.
|
125
|
+
* Is there a simple way to support CQL PROX boolean operator? Not sure. That's a weird operator in CQL, it makes it possible to specify things which make no sense, like onefield = val PROX anotherfield=val2
|
126
|
+
* Support CQL sortBy clauses mapped to Blacklight sort param. We can't tell which solr fields are available for sorting solely from luke, will need additional config to advertise in Explain.
|
127
|
+
* Add CQL relation modifiers on solr.dismax that let you specify arbitrary solr/dismax query parameters. (Add to solr context set too).
|
128
|
+
* Support relation modifier on cql.adj that maps to qs
|
129
|
+
* support CQL context set 'fuzzy' modifier, to have some effect on mm, ps, qs, etc.
|
130
|
+
* *Big one*: Figure out how to embed the explain and advertise the CQL in the BL OpenSearch description (including OpenSearch response in Atom). Tricky from BL architecture to inject this into BL, also some dispute about the best way for the actual XML to look to support this.
|
131
|
+
|
132
|
+
= Use without Blacklight?
|
133
|
+
|
134
|
+
Most of the code in this plugin was written to potentially be useful in other projects, not Blacklight, not neccesarily even Rails. However, the gem initialization code assumes Blacklight in order to insert it's hooks into Blacklight properly. This can probably be refactored to make it easier to use this gem in a non-BL or even non-Rails app, let me know if you are someone who has an actual need/plan for this, and I can possibly help.
|
135
|
+
|
136
|
+
= Acknolwedgements
|
137
|
+
|
138
|
+
Thanks to Chick Markley for writing the CqlRuby gem that provides the fundamental functionality here, and for making me a committer on the project. Thanks to Mike Taylor for writing the original Java CQL parser that Chick's work was based on.
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
Copyright 2010 Jonathan Rochkind/Johns Hopkins University, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the blacklight_cql plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the blacklight_cql plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'Blacklight-cql'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'jeweler'
|
27
|
+
Jeweler::Tasks.new do |gemspec|
|
28
|
+
gemspec.name = "blacklight_cql"
|
29
|
+
gemspec.summary = "Add CQL query support to a Blacklight app"
|
30
|
+
gemspec.description = "May have parts that can be used outside a Blacklight app too with a big of refactoring, let me know if interested."
|
31
|
+
gemspec.email = "rochkind@jhu.edu"
|
32
|
+
gemspec.homepage = "http://github.com/projectblacklight/blacklight_cql"
|
33
|
+
gemspec.authors = ["Jonathan Rochkind"]
|
34
|
+
|
35
|
+
gemspec.add_dependency("cql-ruby", ">=0.8.1")
|
36
|
+
|
37
|
+
gemspec.add_development_dependency("markup_validity")
|
38
|
+
end
|
39
|
+
Jeweler::GemcutterTasks.new
|
40
|
+
rescue LoadError
|
41
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
42
|
+
end
|
43
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.0
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rsolr-ext'
|
2
|
+
|
3
|
+
module BlacklightCql
|
4
|
+
class ExplainController < ApplicationController
|
5
|
+
layout false
|
6
|
+
|
7
|
+
def index
|
8
|
+
@luke_response = Blacklight.solr.luke
|
9
|
+
@field_config = Blacklight
|
10
|
+
|
11
|
+
render "explain.xml.builder", :content_type => "application/xml"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module BlacklightCql::ExplainHelper
|
2
|
+
|
3
|
+
# Arg is a Builder instance.
|
4
|
+
def luke_to_explain_index(xml)
|
5
|
+
@luke_response[:fields].each_pair do |solr_field, defn|
|
6
|
+
# only if it's an indexed
|
7
|
+
if defn[:schema].include?("I")
|
8
|
+
xml.index("search" => "true", "scan"=>false, "sort" => false) do
|
9
|
+
xml.title solr_field.to_s
|
10
|
+
xml.map do
|
11
|
+
xml.name(solr_field.to_s, "set" => CqlRuby.to_solr_defaults[:solr_field_prefix])
|
12
|
+
end
|
13
|
+
# What relations do we support for this index?
|
14
|
+
xml.configInfo do
|
15
|
+
["==", "=", ">=", ">", "<", "<=", "<>", "within", "adj", "all", "any"].each do |rel|
|
16
|
+
xml.supports(rel, "type"=>"relation")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Expects an object in @field_config that implements
|
25
|
+
# Blacklight::SearchFields to provide search field config.
|
26
|
+
# argument is a Builder object we can write to.
|
27
|
+
def blacklight_config_to_explain_index(xml)
|
28
|
+
@field_config.search_field_list.each do |search_field|
|
29
|
+
xml.index("search" => "true", "scan" => "false", "sort" => "false") do
|
30
|
+
xml.title search_field[:display_label]
|
31
|
+
xml.map do
|
32
|
+
xml.name(search_field[:key], "set" => CqlRuby.to_solr_defaults[:blacklight_field_prefix])
|
33
|
+
end
|
34
|
+
# What relations do we support for this index? Right now,
|
35
|
+
# just the custom solr.dismax one
|
36
|
+
xml.configInfo do
|
37
|
+
xml.supports("=", "type"=>"relation")
|
38
|
+
xml.supports("solr.dismax", "type"=>"relation")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
xml.instruct!
|
2
|
+
xml.explain("xmlns" => "http://explain.z3950.org/dtd/2.0/") do
|
3
|
+
|
4
|
+
# Made up protocol "http-cql", oh well.
|
5
|
+
xml.serverInfo("protocol" => "http-cql") do
|
6
|
+
xml.host request.host
|
7
|
+
xml.port request.port
|
8
|
+
xml.database url_for(:controller => "/catalog", :action => "index", :search_field => BlacklightCql::SolrHelperExtension.pseudo_search_field[:key])
|
9
|
+
end
|
10
|
+
|
11
|
+
xml.indexInfo do
|
12
|
+
xml.set(:name => CqlRuby.to_solr_defaults[:solr_field_prefix],
|
13
|
+
:identifier =>
|
14
|
+
url_for(:controller => "/catalog",
|
15
|
+
:action => "index",
|
16
|
+
:anchor => "local_solr_field",
|
17
|
+
:only_path => false))
|
18
|
+
xml.set(:name => CqlRuby.to_solr_defaults[:blacklight_field_prefix],
|
19
|
+
:identifier =>
|
20
|
+
url_for(:controller => "/catalog",
|
21
|
+
:action => "index",
|
22
|
+
:anchor => "local_app_field",
|
23
|
+
:only_path => false))
|
24
|
+
#context set for our solr.dismax relation
|
25
|
+
xml.set(:name => "solr", :identifier => "http://purl.org/net/cql-context-set/solr")
|
26
|
+
|
27
|
+
blacklight_config_to_explain_index(xml)
|
28
|
+
luke_to_explain_index(xml)
|
29
|
+
end
|
30
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Fix this for Rails3, this is a weird way to do it, but oh well.
|
2
|
+
# We want to map catalog/explain but not touch the app's own mappings
|
3
|
+
# for catalog resource. So we do it with just old style routing.
|
4
|
+
|
5
|
+
ActionController::Routing::Routes.draw do |map|
|
6
|
+
map.connect 'catalog/explain', :controller => "blacklight_cql/explain", :action => "index"
|
7
|
+
|
8
|
+
|
9
|
+
end
|
10
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
File.dirname(__FILE__) + "/rails/init".
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'cql_ruby'
|
2
|
+
|
3
|
+
# Patch CqlRuby to provide a #to_bl_solr implementation, which is like
|
4
|
+
# to_solr, but aware of Blacklight search_field defintions for dismax relation
|
5
|
+
# queries.
|
6
|
+
#
|
7
|
+
# The relation "solr.dismax" is used to mean that the value string is a
|
8
|
+
# query in the solr dismax query parsing language.
|
9
|
+
#
|
10
|
+
# CQL indexes can refer to direct solr fields, or Blacklight-defined dismax
|
11
|
+
# search_fields, specified by CQL prefix. By default, "lsolr" means a local
|
12
|
+
# solr field, and "local" means a blacklight search_field. Indexes specified
|
13
|
+
# with no index will use a blacklight search_field if one exists, otherwise
|
14
|
+
# assumed to be solr field.
|
15
|
+
#
|
16
|
+
# "Context set" prefixed used to identify bl and solr fields
|
17
|
+
# can be changed in CqlRuby.to_solr_defaults, keys solr_field_prefix,
|
18
|
+
# blacklight_field_prefix.
|
19
|
+
#
|
20
|
+
# argument to #to_bl_solr is a 'config' object that duck-types to
|
21
|
+
# Blacklight::SearchFields , for accessing search fields config.
|
22
|
+
module CqlRuby
|
23
|
+
to_solr_defaults[:solr_field_prefix] = "lsolr"
|
24
|
+
to_solr_defaults[:blacklight_field_prefix] = "local"
|
25
|
+
to_solr_defaults[:dismax_relation] = "solr.dismax"
|
26
|
+
|
27
|
+
|
28
|
+
CqlNode.send(:include,
|
29
|
+
Module.new do
|
30
|
+
def to_bl_solr(config)
|
31
|
+
raise CqlException.new("#to_bl_solr not supported for #{self.class}: #{self.to_cql}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
CqlBooleanNode.send(:include,
|
38
|
+
Module.new do
|
39
|
+
def to_bl_solr(config)
|
40
|
+
"( #{@left_node.to_bl_solr(config) } #{@modifier_set.to_solr} #{@right_node.to_bl_solr(config) } )"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
CqlTermNode.send(:include,
|
47
|
+
Module.new do
|
48
|
+
def to_bl_solr(config)
|
49
|
+
(index_prefix, index) = parse_index(@index, config)
|
50
|
+
|
51
|
+
if (index_prefix == CqlRuby.to_solr_defaults[:blacklight_field_prefix])
|
52
|
+
field_def = config.search_field_def_for_key(index)
|
53
|
+
|
54
|
+
# Merge together solr params and local ones; they're ALL
|
55
|
+
# going to be provided as LocalParams in our nested query.
|
56
|
+
# Merge so :solr_local_parameters take precedence.
|
57
|
+
per_field_params = (field_def[:solr_parameters] || {}).merge( field_def[:solr_local_parameters] || {} )
|
58
|
+
|
59
|
+
local_params =
|
60
|
+
per_field_params.collect do |key, val|
|
61
|
+
key.to_s + "=" + BlacklightCql.solr_param_quote(val)
|
62
|
+
end.join(" ")
|
63
|
+
|
64
|
+
relation = @relation.modifier_set.base
|
65
|
+
relation = "solr.dismax" if relation == "=" # default server choice
|
66
|
+
relation = "cql.#{relation}" unless relation.index(".") || ["<>", "<=", ">=", "<", ">", "=", "=="].include?(relation)
|
67
|
+
|
68
|
+
|
69
|
+
case relation
|
70
|
+
when "solr.dismax"
|
71
|
+
return " _query_:\"{!dismax #{local_params}} #{BlacklightCql.escape_quotes(@term)} \" "
|
72
|
+
else
|
73
|
+
raise CqlException.new("relation #{relation} not supported for #{CqlRuby.to_solr_defaults[:blacklight_field_prefix]} indexes: #{self.to_cql}")
|
74
|
+
end
|
75
|
+
|
76
|
+
elsif (index_prefix == CqlRuby.to_solr_defaults[:solr_field_prefix])
|
77
|
+
return to_solr
|
78
|
+
else
|
79
|
+
raise CqlException.new("Index prefix not recognized: #{index_prefix}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
# Parse CQL index string into namespace and base name, filling in default
|
87
|
+
# namespace. Returns array of [namespace, basename]
|
88
|
+
def parse_index(index, config)
|
89
|
+
index_prefix, index = @index.index(".") ?
|
90
|
+
@index.split(".", 2) :
|
91
|
+
[nil, @index]
|
92
|
+
|
93
|
+
#for cql.serverChoice and dismax relation, we do dismax with
|
94
|
+
#default field, otherwise we'll let it fall through to #to_solr
|
95
|
+
if( @index.downcase == "cql.serverchoice" &&
|
96
|
+
[CqlRuby.to_solr_defaults[:dismax_relation].downcase,
|
97
|
+
"="].include?(@relation.modifier_set.base.downcase)
|
98
|
+
)
|
99
|
+
index_prefix = CqlRuby.to_solr_defaults[:blacklight_index_prefix]
|
100
|
+
index = config.default_search_field[:key]
|
101
|
+
end
|
102
|
+
# If no prefix, we use local one if we can
|
103
|
+
if ( index_prefix == nil)
|
104
|
+
if ( config.search_field_def_for_key(index) )
|
105
|
+
index_prefix = CqlRuby.to_solr_defaults[:blacklight_field_prefix]
|
106
|
+
else
|
107
|
+
index_prefix = CqlRuby.to_solr_defaults[:solr_field_prefix]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
return [index_prefix, index]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
)
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# We over-ride methods on CatalogController simply by include'ing this
|
2
|
+
# module into CatalogController, which this plugins setup will do.
|
3
|
+
#
|
4
|
+
# This works ONLY becuase the methods we're over-riding come from
|
5
|
+
# a module themsleves (SolrHelper) -- if they were defined on CatalogController
|
6
|
+
# itself, it would not, and we'd have to use some ugly monkey patching
|
7
|
+
# alias_method_chain instead, thankfully we do not.
|
8
|
+
module BlacklightCql::SolrHelperExtension
|
9
|
+
mattr_accessor :pseudo_search_field
|
10
|
+
self.pseudo_search_field = {:key => "cql", :display_label => "External Search (CQL)", :include_in_simple_select => false}
|
11
|
+
|
12
|
+
# Over-ride solr_search_params to do special CQL-to-complex-solr-query
|
13
|
+
# processing iff the "search_field" is our pseudo-search-field indicating
|
14
|
+
# a CQL query.
|
15
|
+
def solr_search_params(extra_controller_params = {})
|
16
|
+
solr_params = super(extra_controller_params)
|
17
|
+
|
18
|
+
if params["search_field"] == self.pseudo_search_field[:key] && ! params["q"].blank?
|
19
|
+
parser = CqlRuby::CqlParser.new
|
20
|
+
solr_params[:q] = "{!lucene} " + parser.parse( params["q"] ).to_bl_solr(Blacklight)
|
21
|
+
end
|
22
|
+
return solr_params
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# We can over-ride a default Blacklight template helper and still call
|
2
|
+
# super on it, by inserting this module as a helper into CatalogController.
|
3
|
+
# This plugins setup will do that.
|
4
|
+
module BlacklightCql::TemplateHelperExtension
|
5
|
+
|
6
|
+
# Make sure the CQL pseudo-search_field is included in the 'select'
|
7
|
+
# when we're displaying a CQL search, so the select makes sense.
|
8
|
+
def search_fields
|
9
|
+
field = BlacklightCql::SolrHelperExtension.pseudo_search_field
|
10
|
+
|
11
|
+
if params[:q].blank? || params[:search_field] != field[:key]
|
12
|
+
super
|
13
|
+
else
|
14
|
+
super.clone.push([field[:display_label], field[:key]]).uniq
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Blacklight-cql
|
2
|
+
|
3
|
+
require 'blacklight_cql/blacklight_to_solr'
|
4
|
+
|
5
|
+
module BlacklightCql
|
6
|
+
mattr_accessor :cql_search_field_key
|
7
|
+
self.cql_search_field_key = "cql"
|
8
|
+
|
9
|
+
# Escape single or double quote marks with backslash
|
10
|
+
def self.escape_quotes(input)
|
11
|
+
input.gsub("'", "\\\'").gsub('"', "\\\"")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Escapes value for Solr LocalParam. Will wrap in
|
15
|
+
# quotes only if needed (if not needed, and the value
|
16
|
+
# turns out to have been a $param, then quotes will mess
|
17
|
+
# things up!), and escapes value inside quotes.
|
18
|
+
def self.solr_param_quote(val)
|
19
|
+
unless val =~ /^[a-zA-Z$_\-\^]+$/
|
20
|
+
val = "'" + escape_quotes(val) + "'"
|
21
|
+
end
|
22
|
+
return val
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Include hook code here
|
2
|
+
require 'cql_ruby'
|
3
|
+
require 'blacklight_cql'
|
4
|
+
require 'dispatcher'
|
5
|
+
|
6
|
+
|
7
|
+
# Call in after_initialze to make sure the default search_fields are
|
8
|
+
# already created, AND the local app has had the opportunity to customize
|
9
|
+
# our placeholder search_field.
|
10
|
+
config.after_initialize do
|
11
|
+
Blacklight.config[:search_fields] << BlacklightCql::SolrHelperExtension.pseudo_search_field
|
12
|
+
end
|
13
|
+
|
14
|
+
# Wrapping in Dispatcher.to_prepare will, theoretically, take care of things
|
15
|
+
# working properly even in development mode with cache_classes=false (per-request
|
16
|
+
# class reloading).
|
17
|
+
Dispatcher.to_prepare("blacklight_cql.setup") do
|
18
|
+
|
19
|
+
|
20
|
+
#Check in case CatalogController _hasn't_ really been re-loaded
|
21
|
+
unless (CatalogController.kind_of?( BlacklightCql::SolrHelperExtension ))
|
22
|
+
# Will over-ride #solr_params to deal with CQL
|
23
|
+
CatalogController.send(:include, BlacklightCql::SolrHelperExtension)
|
24
|
+
|
25
|
+
# Will over-ride helper methods for search form select, to ensure
|
26
|
+
# query is echo'd properly.
|
27
|
+
CatalogController.send(:helper, BlacklightCql::TemplateHelperExtension)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Allow customization of the rails framework path
|
2
|
+
RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT)
|
3
|
+
|
4
|
+
# Don't change this file!
|
5
|
+
# Configure your app in config/environment.rb and config/environments/*.rb
|
6
|
+
|
7
|
+
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
class << self
|
11
|
+
def boot!
|
12
|
+
unless booted?
|
13
|
+
preinitialize
|
14
|
+
pick_boot.run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def booted?
|
19
|
+
defined? Rails::Initializer
|
20
|
+
end
|
21
|
+
|
22
|
+
def pick_boot
|
23
|
+
(vendor_rails? ? VendorBoot : GemBoot).new
|
24
|
+
end
|
25
|
+
|
26
|
+
def vendor_rails?
|
27
|
+
File.exist?(RAILS_FRAMEWORK_ROOT)
|
28
|
+
end
|
29
|
+
|
30
|
+
def preinitialize
|
31
|
+
load(preinitializer_path) if File.exist?(preinitializer_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def preinitializer_path
|
35
|
+
"#{RAILS_ROOT}/config/preinitializer.rb"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Boot
|
40
|
+
def run
|
41
|
+
load_initializer
|
42
|
+
Rails::Initializer.run(:set_load_path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class VendorBoot < Boot
|
47
|
+
def load_initializer
|
48
|
+
require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer"
|
49
|
+
Rails::Initializer.run(:install_gem_spec_stubs)
|
50
|
+
Rails::GemDependency.add_frozen_gem_path
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class GemBoot < Boot
|
55
|
+
def load_initializer
|
56
|
+
self.class.load_rubygems
|
57
|
+
load_rails_gem
|
58
|
+
require 'initializer'
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_rails_gem
|
62
|
+
if version = self.class.gem_version
|
63
|
+
gem 'rails', version
|
64
|
+
else
|
65
|
+
gem 'rails'
|
66
|
+
end
|
67
|
+
rescue Gem::LoadError => load_error
|
68
|
+
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
def rubygems_version
|
74
|
+
Gem::RubyGemsVersion rescue nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def gem_version
|
78
|
+
if defined? RAILS_GEM_VERSION
|
79
|
+
RAILS_GEM_VERSION
|
80
|
+
elsif ENV.include?('RAILS_GEM_VERSION')
|
81
|
+
ENV['RAILS_GEM_VERSION']
|
82
|
+
else
|
83
|
+
parse_gem_version(read_environment_rb)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_rubygems
|
88
|
+
require 'rubygems'
|
89
|
+
min_version = '1.3.1'
|
90
|
+
unless rubygems_version >= min_version
|
91
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
|
95
|
+
rescue LoadError
|
96
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_gem_version(text)
|
101
|
+
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def read_environment_rb
|
106
|
+
environment_rb = "#{RAILS_ROOT}/config/environment.rb"
|
107
|
+
environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb)
|
108
|
+
File.read(environment_rb)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# All that for this:
|
115
|
+
Rails.boot!
|