cql-ruby 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cql_ruby/cql_to_solr.rb +139 -36
- data/test/helper.rb +9 -0
- data/test/test_cql_generator.rb +2 -2
- data/test/test_cql_to_solr.rb +104 -15
- metadata +30 -42
- data/History.txt +0 -4
- data/License.txt +0 -20
- data/Manifest.txt +0 -37
- data/Rakefile +0 -4
- data/config/hoe.rb +0 -70
- data/config/requirements.rb +0 -15
- data/log/debug.log +0 -0
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/script/txt2html +0 -74
- data/setup.rb +0 -1585
- data/tasks/deployment.rake +0 -34
- data/tasks/environment.rake +0 -7
- data/tasks/website.rake +0 -17
- data/test/fixtures/sample_queries.txt +0 -112
- data/website/index.html +0 -111
- data/website/index.txt +0 -49
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.html.erb +0 -48
data/lib/cql_ruby/cql_to_solr.rb
CHANGED
@@ -1,57 +1,160 @@
|
|
1
|
+
# This file adds #to_solr method to CqlRuby::CqlNodes, to
|
2
|
+
# convert parsed CQL to a query in the lucene-solr syntax.
|
3
|
+
# http://wiki.apache.org/solr/SolrQuerySyntax
|
4
|
+
#
|
5
|
+
# CQL version 1.2 spec was used to understand CQL semantics, although this
|
6
|
+
# will likely work for other CQL versions as well.
|
7
|
+
#
|
8
|
+
# All indexes specified in CQL are mapped to Solr fields, assumed to exist,
|
9
|
+
# with the same name as CQL index. Any 'context set' namespace prefixes
|
10
|
+
# on indexes are ignored, just the base name is mapped to solr field.
|
11
|
+
|
12
|
+
# The server-choice index specifications cql.anyindexes, cql.serverchoice,
|
13
|
+
# cql.keywords all map by default to no specified index (let
|
14
|
+
# solr use default from perhaps a solr 'df' param), but this can be changed
|
15
|
+
# with CqlRuby.to_solr_defaults[:default_index]. cql.allindexes maps
|
16
|
+
# by default to 'text', which can be changed with
|
17
|
+
# CqlRuby.to_solr_defaults[:all_index]
|
18
|
+
#
|
19
|
+
# Not all CQL can currently be converted to a solr query. If the CQL includes
|
20
|
+
# nodes that can not be converted, an exception will be raised.
|
21
|
+
#
|
22
|
+
# == CQL expressions that can be converted: ==
|
23
|
+
# * expressions using the following relations, which can be specified with "cql" prefix or without.
|
24
|
+
# ** adj
|
25
|
+
# ** all
|
26
|
+
# ** any
|
27
|
+
# ** == (note, for typical tokenized solr fields, this will be the same as adj, which is not quite proper CQL semantics, but best we can do on an arbitrary solr field).
|
28
|
+
# ** <> (note, for typical tokenized solr fields, won't have quite the right CQL semantics, instead of "not exactly equal to", it will be "does not contain the phrase").
|
29
|
+
# ** >, <, <=, >=, within (note, solr range/comparison queries may or may not actually produce anything sensical depending on solr field definition, but CQL to_solr will translate into solr range syntax anyway and hope for the best. )
|
30
|
+
# ** =, the server's choice relation, defaults to 'adj', but can be specified in CqlRuby.to_solr_defaults.
|
31
|
+
# * CQL Boolean Operators AND, OR, and NOT.
|
32
|
+
#
|
33
|
+
# == CQL expressions that can NOT be converted (at least in present version) ==
|
34
|
+
# And will raise exceptions if you try to call #to_solr on a CQL node which
|
35
|
+
# includes or has children that include the following:
|
36
|
+
# * PROX (boolean) operator.
|
37
|
+
# * cql.encloses relation
|
38
|
+
# * Any relation modifiers.
|
39
|
+
# * Any boolean (operator) modifiers.
|
40
|
+
# * sortBy
|
41
|
+
# * inline prefix map/prefix assignment specification.
|
42
|
+
# * cql.resultsetid
|
43
|
+
|
44
|
+
# == TODO==
|
45
|
+
# * support modifiers on adj relation to map to solr '~' slop param?
|
46
|
+
# * change all tests to rspec instead of Test::Unit
|
47
|
+
# * implemented for acts_as_solr, either more flavors or more general (from chick, jrochkind isn't sure what this means)
|
48
|
+
|
49
|
+
|
1
50
|
module CqlRuby
|
51
|
+
def self.to_solr_defaults
|
52
|
+
@to_solr_params ||= {
|
53
|
+
#What's our default relation for "=" server's choice relation? how about
|
54
|
+
# adj.
|
55
|
+
:default_relation => "cql.adj",
|
56
|
+
# What's our default index for various server's choice index choices?
|
57
|
+
# nil means don't specify an index, let solr take care of it.
|
58
|
+
# Or you can specify one.
|
59
|
+
:default_index => nil,
|
60
|
+
# What index should we use for cql.allIndexes? Again can be nil
|
61
|
+
# meaning let the solr server use it's default.
|
62
|
+
:all_index => "text"
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
|
2
67
|
|
3
|
-
# This set of of overrides to the CqlRuby::CqlNodes provides to_solr methods, where not
|
4
|
-
# specified for a given node the CqlNode.to_solr method will be used
|
5
|
-
# TODO: SOLR can cover much more functionality of CQL than is captured here
|
6
|
-
# TODO: implemented for acts_as_solr, either more flavors or more general
|
7
68
|
|
8
69
|
class CqlNode
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
case quoted_index
|
14
|
-
when 'cql.resultSetId': raise CqlException, "resultSet not supported"
|
15
|
-
when 'cql.allRecords': "[* TO *]"
|
16
|
-
when 'cql.allIndexes': "#{relation_prefix}text:#{quoted_term}"
|
17
|
-
when 'cql.anyIndexes': "#{relation_prefix}text:#{quoted_term}"
|
18
|
-
when 'cql.serverChoice': "#{relation_prefix}#{quoted_term}"
|
19
|
-
else
|
20
|
-
quoted_index.gsub!( /(dc|bath)\./, "" )
|
21
|
-
"#{relation_prefix}#{quoted_index}:#{quoted_term}"
|
22
|
-
end
|
23
|
-
|
70
|
+
# Default, raise not supported, will be implemented by specific
|
71
|
+
# classes where supported.
|
72
|
+
def to_solr
|
73
|
+
raise CqlException.new("#to_solr not supported for #{self.class}: #{self.to_cql}")
|
24
74
|
end
|
25
75
|
end
|
26
76
|
|
27
|
-
|
28
|
-
class CqlRelation
|
29
|
-
def to_solr
|
30
|
-
ms = @modifier_set.to_solr
|
31
|
-
if ms == " <> "
|
32
|
-
return "-"
|
33
|
-
end
|
34
|
-
""
|
35
|
-
end
|
36
|
-
end
|
77
|
+
|
37
78
|
|
38
79
|
class CqlTermNode
|
39
|
-
|
40
|
-
|
41
|
-
|
80
|
+
def to_solr
|
81
|
+
relation = @relation.modifier_set.base
|
82
|
+
|
83
|
+
relation = CqlRuby.to_solr_defaults[:default_relation] if relation == "="
|
84
|
+
# If no prefix to relation, normalize to "cql"
|
85
|
+
relation = "cql.#{relation}" unless relation.index(".") || ["<>", "<=", ">=", "<", ">", "=", "=="].include?(relation)
|
86
|
+
|
87
|
+
|
88
|
+
# What's our default index for server choice indexes? Let's call it
|
89
|
+
# "text".
|
90
|
+
# Otherwise, remove the namespace/"context set" prefix.
|
91
|
+
solr_field = case @index.downcase
|
92
|
+
when "cql.anyindexes", "cql.serverchoice", "cql.keywords"
|
93
|
+
CqlRuby.to_solr_defaults[:default_index]
|
94
|
+
when "cql.allindexes"
|
95
|
+
CqlRuby.to_solr_defaults[:all_index]
|
96
|
+
else
|
97
|
+
@index.gsub(/^[^.]*\./, "")
|
98
|
+
end
|
99
|
+
|
100
|
+
raise CqlException.new("resultSet not supported") if @index.downcase == "cql.resultsetid"
|
101
|
+
raise CqlException.new("relation modifiers not supported: #{@relation.modifier_set.to_cql}") if @relation.modifier_set.modifiers.length > 0
|
102
|
+
|
103
|
+
if index.downcase == "cql.allrecords"
|
104
|
+
#WARNING: Not sure if this will actually always work as intended, its
|
105
|
+
# a bit odd.
|
106
|
+
return "[* TO *]"
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
negate = false
|
111
|
+
|
112
|
+
value =
|
113
|
+
case relation
|
114
|
+
# WARNING: Depending on how you've tokenized, <> and == semantics
|
115
|
+
# may not be fully respected. For typical solr fields, will
|
116
|
+
# match/exclude on partial matches too, not only complete matches.
|
117
|
+
when "<>"
|
118
|
+
negate = true
|
119
|
+
maybe_quote(@term)
|
120
|
+
when "cql.adj", "==": maybe_quote(@term)
|
121
|
+
when "cql.all": '(' + @term.split(/\s/).collect{|a| '+'+a}.join(" ") + ')'
|
122
|
+
when "cql.any": '(' + @term.split(/\s/).join(" OR ") + ')'
|
123
|
+
when ">=": "[" + maybe_quote(@term) + " TO *]"
|
124
|
+
when ">": "{" + maybe_quote(@term) + " TO *}"
|
125
|
+
when "<=": "[* TO " + maybe_quote(@term) + "]"
|
126
|
+
when "<": "{* TO " + maybe_quote(@term) + "}"
|
127
|
+
when "cql.within"
|
128
|
+
bounds = @term.gsub('"', "").split(/\s/)
|
129
|
+
raise CqlException.new("can not extract two bounding values from within relation term: #{@term}") unless bounds.length == 2
|
130
|
+
|
131
|
+
"[" + maybe_quote(bounds[0]) + " TO " + maybe_quote(bounds[1]) + "]"
|
132
|
+
else
|
133
|
+
raise CqlException.new("relation not supported: #{relation}")
|
134
|
+
end
|
135
|
+
|
136
|
+
ret = ""
|
137
|
+
ret += "-" if negate
|
138
|
+
ret += "#{solr_field}:" if solr_field
|
139
|
+
ret += value
|
140
|
+
|
141
|
+
return ret
|
142
|
+
end
|
42
143
|
end
|
43
144
|
|
44
145
|
class CqlBooleanNode
|
45
146
|
def to_solr
|
46
|
-
"(#{@left_node.to_solr}
|
147
|
+
"(#{@left_node.to_solr} #{@modifier_set.to_solr} #{@right_node.to_solr})"
|
47
148
|
end
|
48
149
|
end
|
49
150
|
|
50
151
|
class ModifierSet
|
51
152
|
def to_solr
|
52
|
-
raise CqlException
|
53
|
-
" #{@
|
153
|
+
raise CqlException.new("#to_solr not supported for PROX operator") if @base.upcase == "PROX"
|
154
|
+
raise CqlException.new("#to_solr does not support boolean modifiers: #{to_cql}") if @modifiers.length != 0
|
155
|
+
|
156
|
+
"#{@base.upcase}"
|
54
157
|
end
|
55
158
|
end
|
56
159
|
|
57
|
-
end
|
160
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_cql_generator.rb
CHANGED
@@ -14,8 +14,8 @@ class TestCqlGenerator < Test::Unit::TestCase
|
|
14
14
|
puts tree.to_cql
|
15
15
|
new_tree = parser.parse( tree.to_cql )
|
16
16
|
assert( new_tree )
|
17
|
-
assert( new_tree.to_solr )
|
18
|
-
puts new_tree.to_solr
|
17
|
+
#assert( new_tree.to_solr )
|
18
|
+
#puts new_tree.to_solr
|
19
19
|
# puts tree.to_xcql
|
20
20
|
end
|
21
21
|
puts "done"
|
data/test/test_cql_to_solr.rb
CHANGED
@@ -2,20 +2,109 @@ require 'test/unit'
|
|
2
2
|
require File.dirname(__FILE__) + '/../lib/cql_ruby'
|
3
3
|
|
4
4
|
class CqlToSolrTest < Test::Unit::TestCase
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
5
|
+
@@parser = CqlRuby::CqlParser.new
|
6
|
+
|
7
|
+
def test_boolean
|
8
|
+
assert_to_solr_eq("dog or cat and mammal", '((dog OR cat) AND mammal)')
|
9
|
+
assert_to_solr_eq("dog or (cat and mammal)", '(dog OR (cat AND mammal))')
|
10
|
+
assert_to_solr_eq('dog not cat', "(dog NOT cat)")
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_unsupported_cql
|
14
|
+
assert_can_not_to_solr("cql.resultSetId = dog")
|
15
|
+
assert_can_not_to_solr("field = value PROX field2 = value2")
|
16
|
+
assert_can_not_to_solr("something cql.encloses 2000")
|
17
|
+
assert_can_not_to_solr("field = dog sortBy someField")
|
18
|
+
assert_can_not_to_solr("field unknownrelation value")
|
19
|
+
|
20
|
+
assert_can_not_to_solr("cat or/rel.combine=sum dog")
|
21
|
+
assert_can_not_to_solr("title any/relevant fish")
|
22
|
+
|
23
|
+
assert_can_not_to_solr('> dc = "http://deepcustard.org/" dc.custardDepth > 10')
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_rel_adj
|
27
|
+
assert_to_solr_eq('column cql.adj "one two three"', 'column:"one two three"')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_rel_eq
|
31
|
+
# '==' is same as 'adj', best we can do
|
32
|
+
assert_to_solr_eq('column == "one two three"', @@parser.parse('column adj "one two three"').to_solr)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_rel_any
|
36
|
+
assert_to_solr_eq('column cql.any "one two three"', 'column:(one OR two OR three)')
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_rel_all
|
40
|
+
assert_to_solr_eq('column cql.all "one two three"', 'column:(+one +two +three)')
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_rel_not
|
44
|
+
# Depending on solr schema, this will really map to "does not include phrase", not "does not exactly equal", best we can do.
|
45
|
+
assert_to_solr_eq('column <> "one two three"', '-column:"one two three"')
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_rel_default
|
49
|
+
# '=' defaults to adj
|
50
|
+
assert_to_solr_eq('column = value', @@parser.parse("column adj value").to_solr)
|
51
|
+
|
52
|
+
# unless we set it otherwise
|
53
|
+
with_cql_default(:default_relation, "any") do
|
54
|
+
assert_to_solr_eq('column = value', @@parser.parse("column any value").to_solr)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_range
|
59
|
+
assert_to_solr_eq('column > 100', 'column:{100 TO *}')
|
60
|
+
assert_to_solr_eq('column < 100', 'column:{* TO 100}')
|
61
|
+
assert_to_solr_eq('column >= 100', 'column:[100 TO *]')
|
62
|
+
assert_to_solr_eq('column <= 100', 'column:[* TO 100]')
|
63
|
+
assert_to_solr_eq('column cql.within "100 200"', 'column:[100 TO 200]')
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_drop_index_prefix
|
67
|
+
assert_to_solr_eq("dc.title = frog", @@parser.parse("title = frog").to_solr)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_specified_default_index
|
71
|
+
with_cql_default(:default_index, "default_index") do
|
72
|
+
["cql.anyindexes", "cql.serverchoice", "cql.keywords"].each do |index|
|
73
|
+
assert_to_solr_eq("#{index} = val", @@parser.parse("default_index = val").to_solr)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_all_index
|
79
|
+
assert_to_solr_eq("cql.allindexes = val", @@parser.parse("text = val").to_solr)
|
80
|
+
|
81
|
+
with_cql_default(:all_index, "my_all_index") do
|
82
|
+
assert_to_solr_eq("cql.allindexes = val", @@parser.parse("my_all_index = val").to_solr)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#############
|
87
|
+
# Helpers
|
88
|
+
##############
|
89
|
+
|
90
|
+
def assert_to_solr_eq(cql, should_solr)
|
91
|
+
solr = @@parser.parse(cql).to_solr
|
92
|
+
assert_equal(should_solr, solr)
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_can_not_to_solr(string)
|
96
|
+
assert_raises(CqlRuby::CqlException) do
|
97
|
+
CqlRuby::CqlParser.new.parse(string).to_solr
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def with_cql_default(key, value)
|
102
|
+
old_value = CqlRuby.to_solr_defaults[key]
|
103
|
+
CqlRuby.to_solr_defaults[key] = value
|
104
|
+
begin
|
105
|
+
yield
|
106
|
+
ensure
|
107
|
+
CqlRuby.to_solr_defaults[key] = old_value
|
108
|
+
end
|
20
109
|
end
|
21
110
|
end
|
metadata
CHANGED
@@ -1,58 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cql-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 63
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 8
|
9
|
+
- 0
|
10
|
+
version: 0.8.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
13
|
+
- Jonathan Rochkind
|
7
14
|
- Chick Markley
|
8
15
|
autorequire:
|
9
16
|
bindir: bin
|
10
17
|
cert_chain: []
|
11
18
|
|
12
|
-
date:
|
19
|
+
date: 2010-06-14 00:00:00 -07:00
|
13
20
|
default_executable:
|
14
21
|
dependencies: []
|
15
22
|
|
16
|
-
description: CQL
|
17
|
-
email:
|
18
|
-
- chick@qrhino.com
|
23
|
+
description: " CQL Parser, with serialization from cql node tree to cql, xcql, and solr query"
|
24
|
+
email: cql_ruby@googlegroups.com
|
19
25
|
executables: []
|
20
26
|
|
21
27
|
extensions: []
|
22
28
|
|
23
29
|
extra_rdoc_files:
|
24
|
-
- History.txt
|
25
|
-
- License.txt
|
26
|
-
- Manifest.txt
|
27
30
|
- README.txt
|
28
|
-
- test/fixtures/sample_queries.txt
|
29
|
-
- website/index.txt
|
30
31
|
files:
|
31
|
-
- History.txt
|
32
|
-
- License.txt
|
33
|
-
- Manifest.txt
|
34
|
-
- README.txt
|
35
|
-
- Rakefile
|
36
|
-
- config/hoe.rb
|
37
|
-
- config/requirements.rb
|
38
32
|
- lib/cql_ruby.rb
|
39
|
-
- lib/cql_ruby/version.rb
|
40
33
|
- lib/cql_ruby/cql_generator.rb
|
41
34
|
- lib/cql_ruby/cql_lexer.rb
|
42
35
|
- lib/cql_ruby/cql_nodes.rb
|
43
36
|
- lib/cql_ruby/cql_parser.rb
|
44
37
|
- lib/cql_ruby/cql_to_solr.rb
|
45
|
-
-
|
46
|
-
-
|
47
|
-
-
|
48
|
-
- script/generate
|
49
|
-
- script/txt2html
|
50
|
-
- setup.rb
|
51
|
-
- tasks/deployment.rake
|
52
|
-
- tasks/environment.rake
|
53
|
-
- tasks/website.rake
|
54
|
-
- test/fixtures
|
55
|
-
- test/fixtures/sample_queries.txt
|
38
|
+
- lib/cql_ruby/version.rb
|
39
|
+
- README.txt
|
40
|
+
- test/helper.rb
|
56
41
|
- test/test_cql_generator.rb
|
57
42
|
- test/test_cql_lexer.rb
|
58
43
|
- test/test_cql_nodes.rb
|
@@ -60,39 +45,42 @@ files:
|
|
60
45
|
- test/test_cql_ruby.rb
|
61
46
|
- test/test_cql_to_solr.rb
|
62
47
|
- test/test_helper.rb
|
63
|
-
- website/index.html
|
64
|
-
- website/index.txt
|
65
|
-
- website/javascripts/rounded_corners_lite.inc.js
|
66
|
-
- website/stylesheets/screen.css
|
67
|
-
- website/template.html.erb
|
68
48
|
has_rdoc: true
|
69
|
-
homepage: http://cql-ruby.rubyforge.org
|
49
|
+
homepage: http://cql-ruby.rubyforge.org/
|
50
|
+
licenses: []
|
51
|
+
|
70
52
|
post_install_message:
|
71
53
|
rdoc_options:
|
72
|
-
- --
|
73
|
-
- README.txt
|
54
|
+
- --charset=UTF-8
|
74
55
|
require_paths:
|
75
56
|
- lib
|
76
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
77
59
|
requirements:
|
78
60
|
- - ">="
|
79
61
|
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
80
65
|
version: "0"
|
81
|
-
version:
|
82
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
83
68
|
requirements:
|
84
69
|
- - ">="
|
85
70
|
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
86
74
|
version: "0"
|
87
|
-
version:
|
88
75
|
requirements: []
|
89
76
|
|
90
77
|
rubyforge_project: cql-ruby
|
91
|
-
rubygems_version: 1.
|
78
|
+
rubygems_version: 1.3.7
|
92
79
|
signing_key:
|
93
|
-
specification_version:
|
94
|
-
summary: CQL
|
80
|
+
specification_version: 3
|
81
|
+
summary: CQL Parser
|
95
82
|
test_files:
|
83
|
+
- test/helper.rb
|
96
84
|
- test/test_cql_generator.rb
|
97
85
|
- test/test_cql_lexer.rb
|
98
86
|
- test/test_cql_nodes.rb
|