cql-ruby 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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