user_query 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +4 -0
- data/README +45 -0
- data/Rakefile +359 -0
- data/Releases +6 -0
- data/TODO +0 -0
- data/examples/userqueryex/HOWTO.txt +5 -0
- data/examples/userqueryex/README +183 -0
- data/examples/userqueryex/Rakefile +10 -0
- data/examples/userqueryex/WHAT.txt +16 -0
- data/examples/userqueryex/app/controllers/application.rb +4 -0
- data/examples/userqueryex/app/controllers/entries_controller.rb +68 -0
- data/examples/userqueryex/app/helpers/application_helper.rb +3 -0
- data/examples/userqueryex/app/helpers/entries_helper.rb +2 -0
- data/examples/userqueryex/app/models/entry.rb +8 -0
- data/examples/userqueryex/app/views/entries/_form.rhtml +20 -0
- data/examples/userqueryex/app/views/entries/edit.rhtml +9 -0
- data/examples/userqueryex/app/views/entries/list.rhtml +75 -0
- data/examples/userqueryex/app/views/entries/new.rhtml +8 -0
- data/examples/userqueryex/app/views/entries/show.rhtml +8 -0
- data/examples/userqueryex/app/views/layouts/entries.rhtml +13 -0
- data/examples/userqueryex/config/boot.rb +44 -0
- data/examples/userqueryex/config/database.yml +36 -0
- data/examples/userqueryex/config/environment.rb +54 -0
- data/examples/userqueryex/config/environments/development.rb +21 -0
- data/examples/userqueryex/config/environments/production.rb +18 -0
- data/examples/userqueryex/config/environments/test.rb +19 -0
- data/examples/userqueryex/config/routes.rb +22 -0
- data/examples/userqueryex/db/migrate/001_entry_migration.rb +16 -0
- data/examples/userqueryex/db/schema.rb +15 -0
- data/examples/userqueryex/doc/README_FOR_APP +2 -0
- data/examples/userqueryex/public/404.html +8 -0
- data/examples/userqueryex/public/500.html +8 -0
- data/examples/userqueryex/public/dispatch.cgi +10 -0
- data/examples/userqueryex/public/dispatch.fcgi +24 -0
- data/examples/userqueryex/public/dispatch.rb +10 -0
- data/examples/userqueryex/public/favicon.ico +0 -0
- data/examples/userqueryex/public/images/rails.png +0 -0
- data/examples/userqueryex/public/javascripts/application.js +2 -0
- data/examples/userqueryex/public/javascripts/controls.js +815 -0
- data/examples/userqueryex/public/javascripts/dragdrop.js +913 -0
- data/examples/userqueryex/public/javascripts/effects.js +958 -0
- data/examples/userqueryex/public/javascripts/prototype.js +2006 -0
- data/examples/userqueryex/public/robots.txt +1 -0
- data/examples/userqueryex/public/stylesheets/scaffold.css +74 -0
- data/examples/userqueryex/script/about +3 -0
- data/examples/userqueryex/script/breakpointer +3 -0
- data/examples/userqueryex/script/console +3 -0
- data/examples/userqueryex/script/destroy +3 -0
- data/examples/userqueryex/script/generate +3 -0
- data/examples/userqueryex/script/performance/benchmarker +3 -0
- data/examples/userqueryex/script/performance/profiler +3 -0
- data/examples/userqueryex/script/plugin +3 -0
- data/examples/userqueryex/script/process/reaper +3 -0
- data/examples/userqueryex/script/process/spawner +3 -0
- data/examples/userqueryex/script/runner +3 -0
- data/examples/userqueryex/script/server +3 -0
- data/examples/userqueryex/test/fixtures/entries.yml +5 -0
- data/examples/userqueryex/test/functional/entries_controller_test.rb +88 -0
- data/examples/userqueryex/test/test_helper.rb +28 -0
- data/examples/userqueryex/test/unit/entry_test.rb +10 -0
- data/lib/user_query.rb +10 -0
- data/lib/user_query/generator.rb +219 -0
- data/lib/user_query/parameters.rb +93 -0
- data/lib/user_query/parser.rb +762 -0
- data/lib/user_query/schema.rb +159 -0
- data/lib/user_query/user_query_version.rb +6 -0
- data/test/parser_test.rb +539 -0
- data/test/schema_test.rb +142 -0
- metadata +148 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
# This class can be used as a stand-in for an ActiveRecord class, while
|
2
|
+
# using the ActionPack input tag helpers and standard ActionPack::Controller
|
3
|
+
# idiom.
|
4
|
+
#
|
5
|
+
module UserQuery
|
6
|
+
|
7
|
+
class Parameters
|
8
|
+
@active_record_errors = false
|
9
|
+
begin
|
10
|
+
# Kernel.require 'active_record'
|
11
|
+
@@active_record_errors = ActiveRecord::Errors
|
12
|
+
rescue Object => err
|
13
|
+
@@active_record_errors = false
|
14
|
+
end
|
15
|
+
|
16
|
+
unless @@active_record_errors
|
17
|
+
# Emulate ActiverRecord::Base errors support
|
18
|
+
class Errors
|
19
|
+
def initialize(obj)
|
20
|
+
@obj = obj
|
21
|
+
@name_errors = { }
|
22
|
+
@errors = nil
|
23
|
+
end
|
24
|
+
def add(name, err)
|
25
|
+
(@name_errors[name] ||= []).push(err)
|
26
|
+
(@errors ||= []).push([name, err])
|
27
|
+
end
|
28
|
+
def [](name)
|
29
|
+
(@name_errors[name] || []).join('\n')
|
30
|
+
end
|
31
|
+
def empty?
|
32
|
+
(! @errors) || @errors.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@@active_record_errors = Errors
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def initialize(*opts)
|
40
|
+
@hash = opts.empty? ? {} : opts[0]
|
41
|
+
@hash ||= { }
|
42
|
+
end
|
43
|
+
|
44
|
+
def _value_hash
|
45
|
+
@hash
|
46
|
+
end
|
47
|
+
|
48
|
+
# Emulate ActiverRecord::Base errors support
|
49
|
+
def errors
|
50
|
+
# See ActiveRecord::Validations
|
51
|
+
@errors ||= @@active_record_errors.new(self)
|
52
|
+
end
|
53
|
+
def self.human_attribute_name(x)
|
54
|
+
x # PUNT
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
@hash[key]
|
60
|
+
end
|
61
|
+
|
62
|
+
# OVERIDE Object
|
63
|
+
def id
|
64
|
+
@hash[:id]
|
65
|
+
end
|
66
|
+
def id=(x)
|
67
|
+
@hash[:id] = x
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to?(meth)
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(method, *args)
|
75
|
+
m = method.to_s.clone
|
76
|
+
if m.sub!(/=$/, '') && args.size == 1
|
77
|
+
$stderr.puts "set #{method.inspect} #{args[0].inspect}" if @verbose
|
78
|
+
result = @hash[m.intern] = args[0]
|
79
|
+
elsif m.sub!(/_before_type_cast$/, '') && args.size == 0
|
80
|
+
$stderr.puts "get #{method.inspect}" if @verbose
|
81
|
+
result = @hash[m.intern]
|
82
|
+
elsif args.size == 0
|
83
|
+
$stderr.puts "get #{method.inspect}" if @verbose
|
84
|
+
result = @hash[method]
|
85
|
+
else
|
86
|
+
raise NotImplementedError, method
|
87
|
+
end
|
88
|
+
|
89
|
+
result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end # module
|
@@ -0,0 +1,762 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module UserQuery
|
4
|
+
|
5
|
+
# Do:
|
6
|
+
# require 'currency'
|
7
|
+
# for :type => :money
|
8
|
+
#
|
9
|
+
|
10
|
+
class Parser
|
11
|
+
@now = nil
|
12
|
+
@@now = nil
|
13
|
+
@today = nil
|
14
|
+
@@today= nil
|
15
|
+
@this_year = nil
|
16
|
+
@@this_year = nil
|
17
|
+
|
18
|
+
class Error < Exception; end
|
19
|
+
class SyntaxError < Error; end
|
20
|
+
|
21
|
+
attr_accessor :default_join_op
|
22
|
+
attr_accessor :default_literal_op
|
23
|
+
attr_accessor :keywords
|
24
|
+
attr_accessor :type
|
25
|
+
attr_accessor :expr
|
26
|
+
attr_accessor :verbose
|
27
|
+
|
28
|
+
def initialize(*opts)
|
29
|
+
self.verbose = false
|
30
|
+
self.default_join_op = :and
|
31
|
+
self.default_literal_op = :like
|
32
|
+
self.keywords = [ ]
|
33
|
+
self.type = :string
|
34
|
+
opts = Hash[*opts]
|
35
|
+
opts.each{|k, v| self.send("#{k}=", v)}
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse(str)
|
39
|
+
return nil if str.nil?
|
40
|
+
|
41
|
+
str = str.to_s
|
42
|
+
|
43
|
+
@level = 0
|
44
|
+
@input_original = str.clone
|
45
|
+
@input = str.clone
|
46
|
+
@lex_next = nil
|
47
|
+
@expr = lex_peek && parse_top_level
|
48
|
+
|
49
|
+
eat_whitespace
|
50
|
+
|
51
|
+
@lex_extra = lex_peek
|
52
|
+
if @lex_extra
|
53
|
+
raise SyntaxError, "extra characters for #{type.to_s} field at #{@lex_extra[1].inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
if @verbose
|
57
|
+
q_sql = UserQuery::Generator.new.sql(@expr)
|
58
|
+
$stderr.puts "#{str.inspect} =>\n " + @expr.inspect + " =>\n " + q_sql.inspect
|
59
|
+
end
|
60
|
+
|
61
|
+
@expr
|
62
|
+
end
|
63
|
+
|
64
|
+
def <<(str)
|
65
|
+
parse(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Configuration
|
69
|
+
|
70
|
+
def now=(t)
|
71
|
+
@now = t
|
72
|
+
end
|
73
|
+
def now
|
74
|
+
@now || @@now || Time.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def this_year=(y)
|
78
|
+
@this_year = y.respond_to?(:year) ? y.year : y
|
79
|
+
y
|
80
|
+
end
|
81
|
+
def this_year
|
82
|
+
@this_year || @@this_year || now.year
|
83
|
+
end
|
84
|
+
|
85
|
+
def today=(t)
|
86
|
+
t = t && Time.utc(t.year, t.month, t.day, 0, 0, 0, 0)
|
87
|
+
@today = t
|
88
|
+
end
|
89
|
+
def today
|
90
|
+
t = @today || @@today
|
91
|
+
unless t
|
92
|
+
t = now
|
93
|
+
t = Time.utc(t.year, t.month, t.day, 0, 0, 0, 0)
|
94
|
+
end
|
95
|
+
t
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
# Recursive-descendent parser
|
100
|
+
def parse_top_level
|
101
|
+
show_where("parse_top_level")
|
102
|
+
|
103
|
+
expr = parse_expression
|
104
|
+
|
105
|
+
show_where("parse_top_level", expr)
|
106
|
+
|
107
|
+
expr
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_expression
|
111
|
+
show_where("parse_expression")
|
112
|
+
|
113
|
+
expr = parse_conditional
|
114
|
+
|
115
|
+
show_where("parse_expression", expr)
|
116
|
+
|
117
|
+
expr
|
118
|
+
end
|
119
|
+
|
120
|
+
def parse_conditional
|
121
|
+
show_where("parse_conditional")
|
122
|
+
|
123
|
+
expr = parse_logical_or
|
124
|
+
|
125
|
+
show_where("parse_conditional", expr)
|
126
|
+
|
127
|
+
expr
|
128
|
+
end
|
129
|
+
|
130
|
+
def parse_logical_or
|
131
|
+
show_where("parse_logical_or")
|
132
|
+
|
133
|
+
expr = parse_logical_and
|
134
|
+
|
135
|
+
l = lex_peek
|
136
|
+
case type = l && l[0]
|
137
|
+
when :or
|
138
|
+
lex_read
|
139
|
+
expr = [ :or, expr, parse_logical_or ]
|
140
|
+
end
|
141
|
+
|
142
|
+
show_where("parse_logical_or", expr)
|
143
|
+
|
144
|
+
expr
|
145
|
+
end
|
146
|
+
|
147
|
+
def parse_logical_and
|
148
|
+
show_where("parse_logical_and")
|
149
|
+
|
150
|
+
expr = parse_relational
|
151
|
+
|
152
|
+
l = lex_peek
|
153
|
+
case type = l && l[0]
|
154
|
+
when :and
|
155
|
+
lex_read
|
156
|
+
show_where("parse_logical_and: expr2")
|
157
|
+
expr_2 = parse_logical_and
|
158
|
+
show_where("parse_logical_and: expr2: ", expr_2)
|
159
|
+
expr = [ :and, expr, expr_2 ]
|
160
|
+
end
|
161
|
+
|
162
|
+
show_where("parse_logical_and", expr)
|
163
|
+
|
164
|
+
expr
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def parse_relational
|
169
|
+
show_where("parse_relational")
|
170
|
+
|
171
|
+
expr = lex_peek
|
172
|
+
case op = expr && expr[0]
|
173
|
+
when :eq
|
174
|
+
lex_read
|
175
|
+
expr = ranged_value_eq(parse_literal)
|
176
|
+
|
177
|
+
when :ne
|
178
|
+
lex_read
|
179
|
+
expr = ranged_value_ne(parse_literal)
|
180
|
+
|
181
|
+
when :gt
|
182
|
+
lex_read
|
183
|
+
expr = ranged_value_gt(parse_literal)
|
184
|
+
|
185
|
+
when :lt, :ge
|
186
|
+
lex_read
|
187
|
+
expr = [ op, parse_literal ]
|
188
|
+
|
189
|
+
when :le
|
190
|
+
lex_read
|
191
|
+
expr = ranged_value_le(parse_literal)
|
192
|
+
|
193
|
+
when :between
|
194
|
+
lex_read
|
195
|
+
expr_1 = parse_literal
|
196
|
+
lex_eat(:and, :elipsis)
|
197
|
+
expr_2 = parse_literal(*range_types(expr_1[0]))
|
198
|
+
expr = [ op, expr_1, expr_2 ]
|
199
|
+
|
200
|
+
else
|
201
|
+
expr = parse_unary
|
202
|
+
end
|
203
|
+
|
204
|
+
show_where("parse_relational", expr)
|
205
|
+
|
206
|
+
expr
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_unary
|
210
|
+
show_where("parse_unary")
|
211
|
+
|
212
|
+
expr = lex_peek
|
213
|
+
case op = expr && expr[0]
|
214
|
+
when :not
|
215
|
+
lex_read
|
216
|
+
expr = [ op, parse_sequence ]
|
217
|
+
when :like
|
218
|
+
lex_read
|
219
|
+
expr = [ op, to_string(parse_literal) ]
|
220
|
+
else
|
221
|
+
expr = parse_sequence
|
222
|
+
end
|
223
|
+
|
224
|
+
show_where("parse_unary", expr)
|
225
|
+
|
226
|
+
expr
|
227
|
+
end
|
228
|
+
|
229
|
+
def parse_sequence
|
230
|
+
show_where("parse_sequence")
|
231
|
+
|
232
|
+
expr = parse_primary
|
233
|
+
|
234
|
+
while l = lex_match(:word, :string, :number, :money, :year, :month, :date, :hour, :minute)
|
235
|
+
expr = [ default_join_op, expr, parse_primary ]
|
236
|
+
end
|
237
|
+
|
238
|
+
show_where("parse_sequence", expr)
|
239
|
+
|
240
|
+
expr
|
241
|
+
end
|
242
|
+
|
243
|
+
def parse_primary
|
244
|
+
show_where("parse_primary")
|
245
|
+
|
246
|
+
expr = lex_peek
|
247
|
+
case type = expr && expr[0]
|
248
|
+
when '('
|
249
|
+
lex_read
|
250
|
+
expr = parse_expression
|
251
|
+
lex_eat(')')
|
252
|
+
|
253
|
+
else
|
254
|
+
expr = parse_singular
|
255
|
+
end
|
256
|
+
|
257
|
+
show_where("parse_primary", expr)
|
258
|
+
|
259
|
+
expr
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def parse_singular
|
264
|
+
show_where("parse_singular")
|
265
|
+
|
266
|
+
expr = parse_literal
|
267
|
+
|
268
|
+
l = lex_peek
|
269
|
+
case type = l && l[0]
|
270
|
+
when :elipsis
|
271
|
+
lex_read
|
272
|
+
expr = [ :between, expr, parse_literal ]
|
273
|
+
else
|
274
|
+
# Literals are enconsed with the default literal op
|
275
|
+
#
|
276
|
+
case type = expr && expr[0]
|
277
|
+
when :number, :boolean, :money
|
278
|
+
# Numericals should match exact.
|
279
|
+
expr = [ :eq, expr ]
|
280
|
+
|
281
|
+
when :string, :word
|
282
|
+
# Strings are usually inexact so we use the default literal op.
|
283
|
+
expr = [ default_literal_op, expr ]
|
284
|
+
|
285
|
+
when :year, :month, :day, :hour, :minute, :second
|
286
|
+
# datetime is inexact because intutively
|
287
|
+
# a date should match all values with that date,
|
288
|
+
# but datetime values have greater precision than just date
|
289
|
+
# So we translate year, month, day to intuitive ranges.
|
290
|
+
expr = [ :range, expr, ranged_value_plus_1(expr) ]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
show_where("parse_singular", expr)
|
295
|
+
|
296
|
+
expr
|
297
|
+
end
|
298
|
+
|
299
|
+
def parse_literal(*types)
|
300
|
+
types = types.flatten
|
301
|
+
show_where("parse_literal")
|
302
|
+
|
303
|
+
expr = lex_peek(*types)
|
304
|
+
|
305
|
+
case type = expr && expr[0]
|
306
|
+
when :null, :string, :word, :number, :boolean, :money
|
307
|
+
expr = lex_read(types)
|
308
|
+
|
309
|
+
when :year, :month, :day, :hour, :minute, :second
|
310
|
+
expr = lex_read(types)
|
311
|
+
|
312
|
+
else
|
313
|
+
raise SyntaxError, "Unexpected #{expr.inspect}"
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
show_where("parse_literal", expr)
|
318
|
+
|
319
|
+
expr
|
320
|
+
end
|
321
|
+
|
322
|
+
############################################################
|
323
|
+
# Ranged values
|
324
|
+
#
|
325
|
+
|
326
|
+
# :datetime is a ranged value type.
|
327
|
+
# because intutively a date should match all DATETIME values within
|
328
|
+
# that date, regardless of how much precision
|
329
|
+
# DATETIME has, i.e. down to the second.
|
330
|
+
#
|
331
|
+
# For example:
|
332
|
+
#
|
333
|
+
# The query:
|
334
|
+
#
|
335
|
+
# "12/31/2005"
|
336
|
+
#
|
337
|
+
# should generate SQL:
|
338
|
+
#
|
339
|
+
# column >= '2005-12-31 00:00:00' AND column < '2006-01-01 00:00:00'
|
340
|
+
#
|
341
|
+
# The query:
|
342
|
+
#
|
343
|
+
# "!= 12/31/2005"
|
344
|
+
#
|
345
|
+
# should generate SQL:
|
346
|
+
#
|
347
|
+
# column < '2005-12-31 00:00:00' AND column >= '2006-01-01 00:00:00'
|
348
|
+
#
|
349
|
+
# The query:
|
350
|
+
#
|
351
|
+
# "> 12/31/2005"
|
352
|
+
#
|
353
|
+
# should generate SQL:
|
354
|
+
#
|
355
|
+
# column >= '2006-01-01 00:00:00'
|
356
|
+
#
|
357
|
+
|
358
|
+
def range_types(type)
|
359
|
+
case type
|
360
|
+
when :word, :string
|
361
|
+
type = [ :word, :string ]
|
362
|
+
when :year, :month, :day, :hour, :minute, :second
|
363
|
+
type = [ :year, :month, :day, :hour, :minute, :second ]
|
364
|
+
else
|
365
|
+
type = [ type ]
|
366
|
+
end
|
367
|
+
|
368
|
+
type
|
369
|
+
end
|
370
|
+
|
371
|
+
|
372
|
+
def ranged_value_eq(expr)
|
373
|
+
case type = expr && expr[0]
|
374
|
+
when :year, :month, :day, :hour, :minute, :second
|
375
|
+
expr_0 = expr
|
376
|
+
expr_1 = ranged_value_plus_1(expr)
|
377
|
+
expr = [ :range, expr_0, expr_1 ]
|
378
|
+
|
379
|
+
else
|
380
|
+
expr = [ :eq, expr ]
|
381
|
+
end
|
382
|
+
|
383
|
+
expr
|
384
|
+
end
|
385
|
+
|
386
|
+
def ranged_value_ne(expr)
|
387
|
+
case type = expr && expr[0]
|
388
|
+
when :year, :month, :day, :hour, :minute, :second
|
389
|
+
expr_0 = expr
|
390
|
+
expr_1 = ranged_value_plus_1(expr)
|
391
|
+
expr = [ :or,
|
392
|
+
[ :lt, expr_0 ],
|
393
|
+
[ :ge, expr_1 ],
|
394
|
+
]
|
395
|
+
|
396
|
+
else
|
397
|
+
expr = [ :ne, expr ]
|
398
|
+
end
|
399
|
+
|
400
|
+
expr
|
401
|
+
end
|
402
|
+
|
403
|
+
def ranged_value_gt(expr)
|
404
|
+
case type = expr && expr[0]
|
405
|
+
when :year, :month, :day, :hour, :minute, :second
|
406
|
+
expr = ranged_value_plus_1(expr)
|
407
|
+
expr = [ :ge, expr ]
|
408
|
+
|
409
|
+
else
|
410
|
+
expr = [ :gt, expr ]
|
411
|
+
|
412
|
+
end
|
413
|
+
|
414
|
+
expr
|
415
|
+
end
|
416
|
+
|
417
|
+
def ranged_value_le(expr)
|
418
|
+
case type = expr && expr[0]
|
419
|
+
when :year, :month, :day, :hour, :minute
|
420
|
+
expr = ranged_value_plus_1(expr)
|
421
|
+
expr = [ :lt, expr ]
|
422
|
+
|
423
|
+
else
|
424
|
+
expr = [ :le, expr ]
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
expr
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
def ranged_value_plus_1(expr)
|
433
|
+
case type = expr && expr[0]
|
434
|
+
when :year
|
435
|
+
expr = year_plus_1(expr)
|
436
|
+
|
437
|
+
when :month
|
438
|
+
expr = month_plus_1(expr)
|
439
|
+
|
440
|
+
when :day
|
441
|
+
expr = day_plus_1(expr)
|
442
|
+
|
443
|
+
when :hour
|
444
|
+
expr = hour_plus_1(expr)
|
445
|
+
|
446
|
+
when :minute
|
447
|
+
expr = minute_plus_1(expr)
|
448
|
+
|
449
|
+
when :second
|
450
|
+
expr = second_plus_1(expr)
|
451
|
+
|
452
|
+
end
|
453
|
+
|
454
|
+
expr
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
############################################################
|
459
|
+
# datetime helper
|
460
|
+
#
|
461
|
+
|
462
|
+
def year_plus_1(x)
|
463
|
+
x = x.clone
|
464
|
+
x[2] = x[2] + 1
|
465
|
+
x
|
466
|
+
end
|
467
|
+
|
468
|
+
def month_plus_1(x)
|
469
|
+
x = x.clone
|
470
|
+
x[3] = x[3] + 1
|
471
|
+
if x[3] > 12
|
472
|
+
x[3] = 1
|
473
|
+
x[2] = x[2] + 1
|
474
|
+
end
|
475
|
+
x
|
476
|
+
end
|
477
|
+
|
478
|
+
def day_plus_1(x)
|
479
|
+
# $stderr.puts "day_plus_1(#{x.inspect})"
|
480
|
+
t = Time.utc(x[2], x[3] || 1, x[4] || 1, 0, 0, 0, 0)
|
481
|
+
t = t + (60 * 60 * 25) # Add one day, plus fudge for leap seconds
|
482
|
+
x = [ x[0],
|
483
|
+
x[1], # str
|
484
|
+
t.year,
|
485
|
+
t.month,
|
486
|
+
t.day
|
487
|
+
]
|
488
|
+
x
|
489
|
+
end
|
490
|
+
|
491
|
+
def hour_plus_1(x)
|
492
|
+
t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, 0, 0, 0)
|
493
|
+
t = t + (65 * 60) # Add one hour, plus fudge for leap seconds
|
494
|
+
x = [ x[0],
|
495
|
+
x[1], # str
|
496
|
+
t.year,
|
497
|
+
t.month,
|
498
|
+
t.day,
|
499
|
+
t.hour
|
500
|
+
]
|
501
|
+
x
|
502
|
+
end
|
503
|
+
|
504
|
+
def minute_plus_1(x)
|
505
|
+
t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, x[6] || 0, 0, 0)
|
506
|
+
t = t + 65 # Add one minunte, plus fudge for leap seconds
|
507
|
+
x = [ x[0],
|
508
|
+
x[1], # str
|
509
|
+
t.year,
|
510
|
+
t.month,
|
511
|
+
t.day,
|
512
|
+
t.hour,
|
513
|
+
t.min
|
514
|
+
]
|
515
|
+
x
|
516
|
+
end
|
517
|
+
|
518
|
+
def second_plus_1(x)
|
519
|
+
t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, x[6] || 0, x[7] || 0, 0)
|
520
|
+
t = t + 1 # Add one second
|
521
|
+
x = [ x[0],
|
522
|
+
x[1], # str
|
523
|
+
t.year,
|
524
|
+
t.month,
|
525
|
+
t.day,
|
526
|
+
t.hour,
|
527
|
+
t.min,
|
528
|
+
t.sec
|
529
|
+
]
|
530
|
+
x
|
531
|
+
end
|
532
|
+
|
533
|
+
############################################################
|
534
|
+
# Push back interface w/ lexeme type checks
|
535
|
+
#
|
536
|
+
|
537
|
+
def show_where(str = "", expr = nil)
|
538
|
+
@level = @level - 1 if expr != nil
|
539
|
+
$stderr.puts "#{" " * @level} #{str}: #{lex_peek.inspect} #{@input.inspect} #{expr && ' => '} #{expr && expr.inspect}" if @verbose
|
540
|
+
@level = @level + 1 if expr == nil
|
541
|
+
end
|
542
|
+
|
543
|
+
def lex_match(*types)
|
544
|
+
l = lex_peek
|
545
|
+
types = types.flatten
|
546
|
+
types.include?(l && l[0])
|
547
|
+
end
|
548
|
+
|
549
|
+
def lex_peek(*types)
|
550
|
+
check_types(@lex_next ||= lex, types)
|
551
|
+
end
|
552
|
+
|
553
|
+
def lex_read(*types)
|
554
|
+
l = @lex_next || lex
|
555
|
+
@lex_next = nil
|
556
|
+
check_types(l, types)
|
557
|
+
end
|
558
|
+
|
559
|
+
def lex_eat(*types)
|
560
|
+
l = lex_read
|
561
|
+
check_types(l, types)
|
562
|
+
end
|
563
|
+
|
564
|
+
def check_types(l, types)
|
565
|
+
types = types.flatten
|
566
|
+
unless types.empty?
|
567
|
+
raise SyntaxError, "expected #{types.inspect}, found #{l.inspect}" unless types.include?(l && l[0])
|
568
|
+
end
|
569
|
+
l
|
570
|
+
end
|
571
|
+
|
572
|
+
def to_string(l)
|
573
|
+
l = l.clone;
|
574
|
+
l[0] = :string
|
575
|
+
l[1] = l[1].to_s
|
576
|
+
l[2] = l[2].to_s
|
577
|
+
l
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
############################################################
|
582
|
+
# Low-level lexer
|
583
|
+
#
|
584
|
+
|
585
|
+
def lex
|
586
|
+
# @verbose = true
|
587
|
+
x = @input
|
588
|
+
|
589
|
+
l = nil
|
590
|
+
if eat_whitespace
|
591
|
+
if false
|
592
|
+
l = nil
|
593
|
+
|
594
|
+
elsif md = match(/^NULL\b/)
|
595
|
+
l = [ :null, md[0] ]
|
596
|
+
|
597
|
+
elsif md = match(/^\.\.\./)
|
598
|
+
l = [ :elipsis ]
|
599
|
+
|
600
|
+
elsif md = match(/^(AND\b|[&])/)
|
601
|
+
l = [ :and ]
|
602
|
+
|
603
|
+
elsif md = match(/^(OR\b|[|])/)
|
604
|
+
l = [ :or ]
|
605
|
+
|
606
|
+
elsif md = match(/^BETWEEN\b/)
|
607
|
+
l = [ :between ]
|
608
|
+
|
609
|
+
elsif md = match(/^(NOT\s+EQUAL(\s+TO)?\b|!=|<>)/)
|
610
|
+
l = [ :ne ]
|
611
|
+
|
612
|
+
elsif md = match(/^(LESS\s+THAN\s+ORs\+EQUAL\s+TO\b|<=)/)
|
613
|
+
l = [ :le ]
|
614
|
+
|
615
|
+
elsif md = match(/^(GREATER\s+THAN\s+ORs\+EQUAL\s+TO\b|>=)/)
|
616
|
+
l = [ :ge ]
|
617
|
+
|
618
|
+
elsif md = match(/^(BEFORE\b|LESS(\s+THAN)?\b|<)/)
|
619
|
+
l = [ :lt ]
|
620
|
+
|
621
|
+
elsif md = match(/^(AFTER\b|GREATER(\s+THAN)?\b|>)/)
|
622
|
+
l = [ :gt ]
|
623
|
+
|
624
|
+
elsif md = match(/^(EQUAL(\s+TO)?\b|==?)/)
|
625
|
+
l = [ :eq ]
|
626
|
+
|
627
|
+
elsif md = match(/^(NOT\b|!)/)
|
628
|
+
l = [ :not ]
|
629
|
+
|
630
|
+
elsif md = match(/^(LIKE\b|~)/)
|
631
|
+
l = [ :like, ]
|
632
|
+
|
633
|
+
elsif md = match(/^([\(\)])/)
|
634
|
+
l = [ md[1], md[0] ]
|
635
|
+
|
636
|
+
elsif md = match(/^"((\\.|[^\\"]+)*)"/)
|
637
|
+
l = md[1]
|
638
|
+
l.gsub!(/\\(.)/){|x| $1}
|
639
|
+
@keywords << l
|
640
|
+
l = [ :string, l, l ]
|
641
|
+
|
642
|
+
elsif type == :boolean && (md = match(/^(true)/i))
|
643
|
+
l = [ :boolean, md[0], true ]
|
644
|
+
|
645
|
+
elsif type == :boolean && (md = match(/^(false)/i))
|
646
|
+
l = [ :boolean, md[0], false ]
|
647
|
+
|
648
|
+
elsif type == :money && (md = match(/^(\$?[-+]?([\d,]+(\.\d+)?|\.\d+))/))
|
649
|
+
l = [ :money, md[0], Currency::Money.new(md[1]) ]
|
650
|
+
|
651
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?):?(\d\d):?(\d\d)\s*([ap]?)m?/i))
|
652
|
+
# mm/dd/yyyy hh:mm:ss(am|pm)
|
653
|
+
hh = fix_hh_am_pm(md[5], md[8])
|
654
|
+
mm = md[6].to_i
|
655
|
+
ss = md[7].to_i
|
656
|
+
l = [ :second, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh, mm, ss ]
|
657
|
+
|
658
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?):?(\d\d)\s*([ap]?)m?/i))
|
659
|
+
# mm/dd/yyyy hh:mm(am|pm)
|
660
|
+
hh = fix_hh_am_pm(md[5], md[7])
|
661
|
+
mm = md[6].to_i
|
662
|
+
l = [ :minute, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh, mm ]
|
663
|
+
|
664
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?)\s*([ap]?)m?/i))
|
665
|
+
# mm/dd/yyyy hh
|
666
|
+
hh = fix_hh_am_pm(md[5], md[6])
|
667
|
+
l = [ :hour, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh ]
|
668
|
+
|
669
|
+
elsif type == :datetime && (md = match(/^(\d\d\d\d)\/(\d\d?)\/(\d\d?)/))
|
670
|
+
# yyyy/mm/dd
|
671
|
+
l = [ :day, md[0], md[1].to_i, md[2].to_i, md[3].to_i ]
|
672
|
+
|
673
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)/))
|
674
|
+
# mm/dd/yyyy
|
675
|
+
l = [ :day, md[0], md[3].to_i, md[1].to_i, md[2].to_i ]
|
676
|
+
|
677
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d\d\d)/))
|
678
|
+
# mm/yyyy
|
679
|
+
l = [ :month, md[0], md[2].to_i, md[1].to_i ]
|
680
|
+
|
681
|
+
elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)/))
|
682
|
+
# mm/dd => THIS-YEAR/mm/dd
|
683
|
+
l = [ :day, md[0], this_year, md[1].to_i, md[2].to_i ]
|
684
|
+
|
685
|
+
elsif type == :datetime && (md = match(/^(\d\d\d\d)\/(\d\d?)/))
|
686
|
+
# yyyy/mm
|
687
|
+
l = [ :month, md[0], md[1].to_i, md[2].to_i ]
|
688
|
+
|
689
|
+
elsif type == :datetime && (md = match(/^(\d\d\d\d)/))
|
690
|
+
# yyyy
|
691
|
+
l = [ :year, md[0], md[1].to_i ]
|
692
|
+
|
693
|
+
elsif type == :datetime && (md = match(/^now\b/i))
|
694
|
+
# now
|
695
|
+
l = now
|
696
|
+
l = [ :second, md[0], l.year, l.month, l.day, l.hour, l.min, l.sec ]
|
697
|
+
|
698
|
+
elsif type == :datetime && (md = match(/^this\s*year\b/i))
|
699
|
+
# this year => THIS-YEAR
|
700
|
+
l = [ :year, md[0], this_year ]
|
701
|
+
|
702
|
+
elsif type == :datetime && (md = match(/^yesterday\b|today\s*\-\s*(\d+)/i))
|
703
|
+
# yesterday
|
704
|
+
l = today - (24 * 60 * 60) * (md[1] ? md[1].to_i : 1)
|
705
|
+
l = [ :day, md[0], l.year, l.month, l.day ]
|
706
|
+
|
707
|
+
elsif type == :datetime && (md = match(/^tomorrow\b|today\s*\+\s*(\d+)/i))
|
708
|
+
# tomorrow
|
709
|
+
l = today + (24 * 60 * 60) * (md[1] ? md[1].to_i : 1)
|
710
|
+
l = [ :day, md[0], l.year, l.month, l.day ]
|
711
|
+
|
712
|
+
elsif type == :datetime && (md = match(/^today\b/i))
|
713
|
+
# today
|
714
|
+
l = today
|
715
|
+
l = [ :day, md[0], l.year, l.month, l.day ]
|
716
|
+
|
717
|
+
elsif md = match(/^([-+]?((\d+|\.\d+|\d+\.\d*)[eE][-+]?\d+|\.\d+|\d+\.\d*))/)
|
718
|
+
l = [ :number, md[0], md[1].to_f ]
|
719
|
+
|
720
|
+
elsif md = match(/^([-+]?\d+)/)
|
721
|
+
l = [ :number, md[0], md[1].to_i ]
|
722
|
+
|
723
|
+
elsif type == :string && md = match(/^(\w+)/)
|
724
|
+
@keywords << md[1]
|
725
|
+
l = [ :word, md[0], md[1] ]
|
726
|
+
else
|
727
|
+
raise SyntaxError, "invalid character for #{type.to_s} field at #{@input.inspect}"
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
$stderr.puts "lex #{x.inspect} => #{l.inspect}" if @verbose
|
732
|
+
|
733
|
+
l
|
734
|
+
end
|
735
|
+
|
736
|
+
def fix_hh_am_pm(hh, am_pm)
|
737
|
+
hh = hh.to_i
|
738
|
+
if am_pm
|
739
|
+
am_pm = am_pm.downcase
|
740
|
+
hh = 0 if am_pm == 'a' && hh == 12
|
741
|
+
hh = hh + 12 if am_pm == 'p' && hh != 12
|
742
|
+
end
|
743
|
+
hh
|
744
|
+
end
|
745
|
+
|
746
|
+
def eat_whitespace
|
747
|
+
@input.sub!(/^\s+/, '')
|
748
|
+
! @input.empty?
|
749
|
+
end
|
750
|
+
|
751
|
+
def match(rx)
|
752
|
+
# $stderr.puts "m_a_a @ #{@input.inspect}"
|
753
|
+
if md = rx.match(@input)
|
754
|
+
@input = md.post_match
|
755
|
+
end
|
756
|
+
md
|
757
|
+
end
|
758
|
+
|
759
|
+
end
|
760
|
+
|
761
|
+
end # module
|
762
|
+
|