cql-ruby 0.7.1
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/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +37 -0
- data/README.txt +191 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +15 -0
- data/lib/cql_ruby.rb +9 -0
- data/lib/cql_ruby/cql_generator.rb +133 -0
- data/lib/cql_ruby/cql_lexer.rb +221 -0
- data/lib/cql_ruby/cql_nodes.rb +328 -0
- data/lib/cql_ruby/cql_parser.rb +183 -0
- data/lib/cql_ruby/cql_to_solr.rb +57 -0
- data/lib/cql_ruby/version.rb +9 -0
- data/log/debug.log +0 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/test/fixtures/sample_queries.txt +112 -0
- data/test/test_cql_generator.rb +23 -0
- data/test/test_cql_lexer.rb +54 -0
- data/test/test_cql_nodes.rb +105 -0
- data/test/test_cql_parser.rb +113 -0
- data/test/test_cql_ruby.rb +11 -0
- data/test/test_cql_to_solr.rb +21 -0
- data/test/test_helper.rb +2 -0
- data/website/index.html +111 -0
- data/website/index.txt +49 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +102 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
module CqlRuby
|
2
|
+
|
3
|
+
# This is derived from java based a semi-trivial subclass for java.io.StreamTokenizer that:
|
4
|
+
# * Includes a render() method
|
5
|
+
# * Knows about the multi-character tokens "<=", ">=" and "<>"
|
6
|
+
# * Recognises a set of keywords as tokens in their own right
|
7
|
+
# * Includes some primitive debugging-output facilities
|
8
|
+
# It's used only by CQLParser
|
9
|
+
|
10
|
+
class CqlLexer
|
11
|
+
attr_accessor :tokenizer, :simple_tokens, :index, :token_type, :value
|
12
|
+
|
13
|
+
TT_EOF = 1
|
14
|
+
TT_EOL = 2
|
15
|
+
TT_NUMBER = 3
|
16
|
+
TT_WORD = 4
|
17
|
+
TT_LE = 1000 # The "<=" relation
|
18
|
+
TT_GE = 1001 # The ">=" relation
|
19
|
+
TT_NE = 1002 # The "<>" relation
|
20
|
+
TT_EQEQ = 1003 # The "==" relation
|
21
|
+
TT_AND = 1004 # The "and" boolean
|
22
|
+
TT_OR = 1005 # The "or" boolean
|
23
|
+
TT_NOT = 1006 # The "not" boolean
|
24
|
+
TT_PROX = 1007 # The "prox" boolean
|
25
|
+
TT_SORTBY = 1008 # The "sortby" operator
|
26
|
+
|
27
|
+
@@keywords = { "and" => CqlLexer::TT_AND, "or" => CqlLexer::TT_OR, "not" => CqlLexer::TT_NOT,
|
28
|
+
"prox" => CqlLexer::TT_PROX, "sortby" => CqlLexer::TT_SORTBY}
|
29
|
+
@@symbol_tokens = @@keywords.values + [CqlLexer::TT_WORD,CqlLexer::TT_NUMBER,'"']
|
30
|
+
|
31
|
+
@@ordinary_chars = /[=<>\/()]/
|
32
|
+
@@re_any_token_start = /[\w(\)<>\/="*]/
|
33
|
+
@@re_string_end = /[ \t(\)<>\/="]/
|
34
|
+
|
35
|
+
def initialize( s="", debug=false )
|
36
|
+
debug
|
37
|
+
@simple_tokens = tokenize( s )
|
38
|
+
@index = -1
|
39
|
+
end
|
40
|
+
|
41
|
+
def CqlLexer.symbol_tokens
|
42
|
+
@@symbol_tokens
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_string_end( s, position )
|
46
|
+
s.index( @@re_string_end, position) || s.length
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_quoted_string_end( s, position )
|
50
|
+
is_backslashed = false
|
51
|
+
for i in position+1..s.length
|
52
|
+
if s[i..i] == '\\'
|
53
|
+
is_backslashed = ! is_backslashed
|
54
|
+
else
|
55
|
+
if s[i..i] == '"' and not is_backslashed
|
56
|
+
return i + 1
|
57
|
+
end
|
58
|
+
is_backslashed = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
raise CqlException, "unterminated quoted string at position #{position} in '#{s}'"
|
62
|
+
return s.length
|
63
|
+
end
|
64
|
+
|
65
|
+
def tokenize( cql_string )
|
66
|
+
position = 0
|
67
|
+
previous_backslash = false
|
68
|
+
length = cql_string.length
|
69
|
+
|
70
|
+
@tokens = []
|
71
|
+
while position < length
|
72
|
+
token_begin = cql_string.index( @@re_any_token_start, position )
|
73
|
+
break unless token_begin
|
74
|
+
|
75
|
+
|
76
|
+
case cql_string[token_begin..token_begin]
|
77
|
+
when @@ordinary_chars
|
78
|
+
token_end = token_begin+1
|
79
|
+
when /[\w*]/
|
80
|
+
token_end = find_string_end( cql_string, token_begin )
|
81
|
+
when '"'
|
82
|
+
token_end = find_quoted_string_end( cql_string, token_begin )
|
83
|
+
else
|
84
|
+
token_end = token_begin+1
|
85
|
+
end
|
86
|
+
|
87
|
+
@tokens << cql_string[token_begin..token_end-1]
|
88
|
+
position = token_end
|
89
|
+
end
|
90
|
+
|
91
|
+
# puts "tokens=#{@tokens.inspect}"
|
92
|
+
@tokens
|
93
|
+
end
|
94
|
+
|
95
|
+
def next_token
|
96
|
+
underlying_next_token
|
97
|
+
return CqlLexer::TT_EOF if eof? or eol?
|
98
|
+
|
99
|
+
if @token_type == '<'
|
100
|
+
underlying_next_token
|
101
|
+
if @token_type == '='
|
102
|
+
@token_type = CqlLexer::TT_LE
|
103
|
+
elsif @token_type == ">"
|
104
|
+
@token_type = CqlLexer::TT_NE
|
105
|
+
else
|
106
|
+
push_back
|
107
|
+
@token_type = '<'
|
108
|
+
end
|
109
|
+
elsif @token_type == '>'
|
110
|
+
underlying_next_token
|
111
|
+
if @token_type == '='
|
112
|
+
@token_type = CqlLexer::TT_GE
|
113
|
+
else
|
114
|
+
push_back
|
115
|
+
@token_type = '>'
|
116
|
+
end
|
117
|
+
elsif @token_type == '='
|
118
|
+
underlying_next_token
|
119
|
+
if @token_type == '='
|
120
|
+
@token_type = CqlLexer::TT_EQEQ
|
121
|
+
else
|
122
|
+
push_back
|
123
|
+
@token_type = '='
|
124
|
+
end
|
125
|
+
end
|
126
|
+
@token_type
|
127
|
+
end
|
128
|
+
|
129
|
+
def underlying_next_token
|
130
|
+
@index += 1
|
131
|
+
if @index >= @simple_tokens.length
|
132
|
+
@token_type = CqlLexer::TT_EOF
|
133
|
+
@value = nil
|
134
|
+
return
|
135
|
+
end
|
136
|
+
@value = @simple_tokens[ @index ]
|
137
|
+
if /[0-9]+/ =~ @value
|
138
|
+
@token_type = CqlLexer::TT_NUMBER
|
139
|
+
elsif @value.length > 1
|
140
|
+
if @value.slice(0..0) == '"'
|
141
|
+
@token_type = '"'
|
142
|
+
@value = @value.slice(1..-2)
|
143
|
+
else
|
144
|
+
@token_type = @@keywords[ @value.downcase ] || CqlLexer::TT_WORD
|
145
|
+
end
|
146
|
+
else
|
147
|
+
@token_type = @value
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def push_back
|
152
|
+
@index -= 1
|
153
|
+
end
|
154
|
+
|
155
|
+
def render( token=nil, quote_chars=true )
|
156
|
+
token = @token_type unless token
|
157
|
+
case token
|
158
|
+
when CqlLexer::TT_EOF: "EOF"
|
159
|
+
when CqlLexer::TT_NUMBER: @value
|
160
|
+
when CqlLexer::TT_WORD: "word:#{@value}"
|
161
|
+
when "'": "string:\"#{@value}\""
|
162
|
+
when CqlLexer::TT_LE: "<="
|
163
|
+
when CqlLexer::TT_GE: ">="
|
164
|
+
when CqlLexer::TT_NE: "<>"
|
165
|
+
when CqlLexer::TT_EQEQ: "=="
|
166
|
+
when CqlLexer::TT_EOF: "EOF"
|
167
|
+
else
|
168
|
+
if @@keywords.has_value?( @value )
|
169
|
+
@value
|
170
|
+
else
|
171
|
+
if quote_chars
|
172
|
+
"'#{token}'"
|
173
|
+
else
|
174
|
+
token
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def eof?
|
181
|
+
@token_type == CqlLexer::TT_EOF
|
182
|
+
end
|
183
|
+
def eol?
|
184
|
+
@token_type == CqlLexer::TT_EOL
|
185
|
+
end
|
186
|
+
def number?
|
187
|
+
@token_type == CqlLexer::TT_NUMBER
|
188
|
+
end
|
189
|
+
def word?
|
190
|
+
@token_type == CqlLexer::TT_WORD
|
191
|
+
end
|
192
|
+
def less_than_or_equal?
|
193
|
+
@token_type == CqlLexer::TT_LE
|
194
|
+
end
|
195
|
+
def greater_than_or_equal?
|
196
|
+
@token_type == CqlLexer::TT_GE
|
197
|
+
end
|
198
|
+
def double_equal?
|
199
|
+
@token_type == CqlLexer::TT_EQEQ
|
200
|
+
end
|
201
|
+
def not_equal?
|
202
|
+
@token_type == CqlLexer::TT_NE
|
203
|
+
end
|
204
|
+
def not?
|
205
|
+
@token_type == CqlLexer::TT_NOT
|
206
|
+
end
|
207
|
+
def and?
|
208
|
+
@token_type == CqlLexer::TT_AND
|
209
|
+
end
|
210
|
+
def or?
|
211
|
+
@token_type == CqlLexer::TT_OR
|
212
|
+
end
|
213
|
+
def prox?
|
214
|
+
@token_type == CqlLexer::TT_PROX
|
215
|
+
end
|
216
|
+
def sortby?
|
217
|
+
@token_type == CqlLexer::TT_SORTBY
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
module CqlRuby
|
2
|
+
|
3
|
+
class CqlNode
|
4
|
+
require 'rubygems'
|
5
|
+
require 'builder'
|
6
|
+
def check_xml( xml )
|
7
|
+
xml = Builder::XmlMarkup.new(:indent => 1) unless xml
|
8
|
+
# (0..1).each {|x| puts x}
|
9
|
+
xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def getResultSetName
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# arguments kept for symmetry
|
21
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_cql
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_prefixes( xml=nil, prefixes=nil )
|
30
|
+
return unless prefixes and prefixes.length > 0
|
31
|
+
|
32
|
+
xml = check_xml( xml )
|
33
|
+
xml.prefixes do
|
34
|
+
prefixes.each do |prefix|
|
35
|
+
xml.prefix do
|
36
|
+
xml.name( prefix.name )
|
37
|
+
xml.identifier( prefix.identifier )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_sortkeys( xml=nil, sortkeys=nil )
|
44
|
+
return "" unless sortkeys and sortkeys.any?
|
45
|
+
|
46
|
+
xml = check_xml( xml )
|
47
|
+
xml.sortKeys do
|
48
|
+
sortkeys.each do |key|
|
49
|
+
key.sortkey_to_xcql( xml )
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CqlPrefix
|
56
|
+
attr_accessor :name, :identifier
|
57
|
+
|
58
|
+
def initialize( name, identifier )
|
59
|
+
super()
|
60
|
+
@name = name
|
61
|
+
@identifier = identifier
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class CqlPrefixNode < CqlNode
|
66
|
+
attr_accessor :prefix, :subtree
|
67
|
+
def initialize( name=nil, identifier=nil, new_subtree=nil )
|
68
|
+
super()
|
69
|
+
@prefix = CqlPrefix.new( name, identifier )
|
70
|
+
@subtree = new_subtree
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_cql
|
74
|
+
if @prefix.name
|
75
|
+
">#{@prefix.name}=\"#{@prefix.identifier}\" (#{@subtree.to_cql})"
|
76
|
+
else
|
77
|
+
">\"#{@prefix.identifier}\" (#{@subtree.to_cql})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
82
|
+
xml = check_xml( xml )
|
83
|
+
tmp = []
|
84
|
+
tmp = prefixes.dup if prefixes
|
85
|
+
tmp << @prefix
|
86
|
+
|
87
|
+
@subtree.to_xcql( xml, tmp, sortkeys )
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Modifier < CqlNode
|
92
|
+
attr_accessor :type, :comparison, :value
|
93
|
+
|
94
|
+
def initialize( new_type, new_comparison=nil, new_value=nil)
|
95
|
+
super()
|
96
|
+
@type = new_type
|
97
|
+
@comparison = new_comparison
|
98
|
+
@value = new_value
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_cql
|
102
|
+
res = "#{@type}"
|
103
|
+
res << " #{@comparison} #{@value}" if @value
|
104
|
+
res
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_xcql( xml=nil, relation_element="unknown_relation" )
|
108
|
+
xml = check_xml( xml )
|
109
|
+
xml.modifier do
|
110
|
+
xml.type( @type )
|
111
|
+
if( @value )
|
112
|
+
xml.tag!( relation_element, @comparison ) if @comparison
|
113
|
+
xml.value( @value )
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class ModifierSet < CqlNode
|
120
|
+
attr_accessor :base, :modifiers
|
121
|
+
|
122
|
+
def initialize( base )
|
123
|
+
# super
|
124
|
+
@base = base.dup
|
125
|
+
@modifiers = []
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_modifier( type, comparison=nil, value=nil)
|
129
|
+
modifiers << Modifier.new( type, comparison, value )
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_cql
|
133
|
+
res = @base.dup
|
134
|
+
@modifiers.each do |m|
|
135
|
+
res << "/#{m.to_cql}"
|
136
|
+
end
|
137
|
+
res
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_xcql( xml=nil, top_level_element="unknowElement" )
|
141
|
+
xml = check_xml( xml )
|
142
|
+
underlying_to_xcql( xml, top_level_element, "value" )
|
143
|
+
end
|
144
|
+
|
145
|
+
def sortkey_to_xcql( xml=nil )
|
146
|
+
xml = check_xml( xml )
|
147
|
+
underlying_to_xcql( xml, "key", "index" )
|
148
|
+
end
|
149
|
+
|
150
|
+
def underlying_to_xcql( xml, top_level_element, value_element )
|
151
|
+
xml.tag!( top_level_element ) do
|
152
|
+
xml.tag!( value_element, @base )
|
153
|
+
if @modifiers.any?
|
154
|
+
xml.modifiers do
|
155
|
+
@modifiers.each { |modifier| modifier.to_xcql( xml, "comparison" ) }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class CqlBooleanNode < CqlNode
|
163
|
+
attr_accessor :left_node, :right_node, :modifier_set
|
164
|
+
|
165
|
+
def initialize( left_node, right_node, modifier_set )
|
166
|
+
super()
|
167
|
+
@left_node = left_node.dup
|
168
|
+
@right_node = right_node.dup
|
169
|
+
@modifier_set = modifier_set || ModifierSet.new( "=" )
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
173
|
+
xml = check_xml( xml )
|
174
|
+
xml.triple do
|
175
|
+
render_prefixes( xml, prefixes )
|
176
|
+
modifier_set.to_xcql( xml, "boolean" )
|
177
|
+
xml.leftOperand do
|
178
|
+
left_node.to_xcql( xml )
|
179
|
+
end
|
180
|
+
xml.rightOperand do
|
181
|
+
right_node.to_xcql( xml )
|
182
|
+
end
|
183
|
+
render_sortkeys( xml, sortkeys )
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_cql
|
188
|
+
"(#{@left_node.to_cql}) #{@modifier_set.to_cql} (#{@right_node.to_cql})"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Represents a terminal node in a CQL parse-tree.
|
194
|
+
# A term node consists of the term String itself, together with,
|
195
|
+
# optionally, an index string and a relation. Neither or both of
|
196
|
+
# these must be provided - you can't have an index without a
|
197
|
+
# relation or vice versa.
|
198
|
+
|
199
|
+
class CqlTermNode < CqlNode
|
200
|
+
attr_accessor :index, :relation, :term
|
201
|
+
def initialize( index, relation, term )
|
202
|
+
super()
|
203
|
+
|
204
|
+
@index = index.dup
|
205
|
+
@relation = relation.dup
|
206
|
+
@term = term.dup
|
207
|
+
end
|
208
|
+
|
209
|
+
def result_set_index?( qual )
|
210
|
+
/(srw|cql).resultSet(|Id|Name|SetName)/ =~ qual
|
211
|
+
end
|
212
|
+
|
213
|
+
def result_set_name
|
214
|
+
return term if result_set_index?( @index )
|
215
|
+
nil
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
219
|
+
xml = check_xml( xml )
|
220
|
+
|
221
|
+
xml.searchClause do
|
222
|
+
render_prefixes( xml, prefixes )
|
223
|
+
xml.index( @index )
|
224
|
+
@relation.to_xcql( xml )
|
225
|
+
xml.term( @term )
|
226
|
+
render_sortkeys( xml, sortkeys )
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_cql
|
231
|
+
quoted_index = maybe_quote( @index )
|
232
|
+
quoted_term = maybe_quote( @term )
|
233
|
+
res = quoted_term
|
234
|
+
|
235
|
+
if @index && /(srw|cql)\.serverChoice/i !~ @index
|
236
|
+
res = "#{quoted_index} #{@relation.to_cql} #{quoted_term}"
|
237
|
+
end
|
238
|
+
res
|
239
|
+
end
|
240
|
+
|
241
|
+
def maybe_quote( s )
|
242
|
+
if s == "" || s =~ /[" \t=<>()\/]/
|
243
|
+
return "\"#{s.gsub( /"/, "\\\"" )}\""
|
244
|
+
end
|
245
|
+
s
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class CqlAndNode < CqlBooleanNode
|
250
|
+
def initialize( left_node, right_node, modifier_set )
|
251
|
+
super( left_node, right_node, modifier_set )
|
252
|
+
end
|
253
|
+
end
|
254
|
+
class CqlOrNode < CqlBooleanNode
|
255
|
+
def initialize( left_node, right_node, modifier_set )
|
256
|
+
super( left_node, right_node, modifier_set )
|
257
|
+
end
|
258
|
+
end
|
259
|
+
class CqlNotNode < CqlBooleanNode
|
260
|
+
def initialize( left_node, right_node, modifier_set )
|
261
|
+
super( left_node, right_node, modifier_set )
|
262
|
+
end
|
263
|
+
end
|
264
|
+
class CqlProxNode < CqlBooleanNode
|
265
|
+
def initialize( left_node, right_node, modifier_set )
|
266
|
+
super( left_node, right_node, modifier_set )
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
class CqlRelation < CqlNode
|
271
|
+
attr_accessor :modifier_set
|
272
|
+
|
273
|
+
def initialize( base )
|
274
|
+
super()
|
275
|
+
@modifier_set = ModifierSet.new( base )
|
276
|
+
end
|
277
|
+
|
278
|
+
def set_modifiers( ms )
|
279
|
+
@modifier_set = ms
|
280
|
+
end
|
281
|
+
|
282
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
283
|
+
raise CqlException, "CqlRelation.to_xcql called with no relation" if sortkeys
|
284
|
+
xml = check_xml( xml )
|
285
|
+
@modifier_set.to_xcql( xml, "relation" )
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_cql
|
289
|
+
@modifier_set.to_cql()
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class CqlSortNode < CqlNode
|
294
|
+
attr_accessor :subtree, :keys
|
295
|
+
|
296
|
+
def initialize( subtree )
|
297
|
+
super()
|
298
|
+
@subtree = subtree
|
299
|
+
@keys = []
|
300
|
+
end
|
301
|
+
|
302
|
+
def add_sort_index( key )
|
303
|
+
@keys << key
|
304
|
+
end
|
305
|
+
|
306
|
+
def to_xcql( xml=nil, prefixes=nil, sortkeys=nil )
|
307
|
+
raise CqlException, "CqlSortNode.to_xcql called with sortkeys" if sortkeys and sortkeys.any?
|
308
|
+
|
309
|
+
xml = check_xml( xml )
|
310
|
+
return @subtree.to_xcql( xml, prefixes, @keys )
|
311
|
+
end
|
312
|
+
|
313
|
+
def to_cql
|
314
|
+
buf = @subtree.to_cql
|
315
|
+
if @keys.any?
|
316
|
+
buf << " sortby "
|
317
|
+
@keys.each do |key|
|
318
|
+
buf << " #{key.to_cql}"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
buf
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
class CqlException < RuntimeError
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|