cql-ruby 0.7.1 → 0.8.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.
- 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
|