user_query 0.1.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/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
|
+
|