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.
@@ -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